diff --git a/android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/view/MessageCta.kt b/android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/view/MessageCta.kt index c686c4c2f922..dc44f9374988 100644 --- a/android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/view/MessageCta.kt +++ b/android-design-system/design-system/src/main/java/com/duckduckgo/common/ui/view/MessageCta.kt @@ -26,6 +26,7 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.core.text.HtmlCompat import androidx.core.view.isVisible import com.airbnb.lottie.LottieAnimationView +import com.duckduckgo.common.ui.view.MessageCta.MessageType.REMOTE_LIST_MESSAGE import com.duckduckgo.common.ui.view.MessageCta.MessageType.REMOTE_MESSAGE import com.duckduckgo.common.ui.view.MessageCta.MessageType.REMOTE_PROMO_MESSAGE import com.duckduckgo.common.ui.viewbinding.viewBinding @@ -73,6 +74,7 @@ class MessageCta : FrameLayout { when (message.messageType) { REMOTE_MESSAGE -> setRemoteMessage(message) REMOTE_PROMO_MESSAGE -> setPromoMessage(message) + REMOTE_LIST_MESSAGE -> { } } } @@ -199,5 +201,6 @@ class MessageCta : FrameLayout { enum class MessageType { REMOTE_MESSAGE, REMOTE_PROMO_MESSAGE, + REMOTE_LIST_MESSAGE, } } diff --git a/android-design-system/design-system/src/main/res/drawable/ic_image_ai.xml b/android-design-system/design-system/src/main/res/drawable/ic_image_ai.xml new file mode 100644 index 000000000000..86fd6786412d --- /dev/null +++ b/android-design-system/design-system/src/main/res/drawable/ic_image_ai.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android-design-system/design-system/src/main/res/drawable/ic_key_import.xml b/android-design-system/design-system/src/main/res/drawable/ic_key_import.xml new file mode 100644 index 000000000000..25a32ba9b7f7 --- /dev/null +++ b/android-design-system/design-system/src/main/res/drawable/ic_key_import.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/android-design-system/design-system/src/main/res/drawable/ic_radar.xml b/android-design-system/design-system/src/main/res/drawable/ic_radar.xml new file mode 100644 index 000000000000..7a48614cf8b9 --- /dev/null +++ b/android-design-system/design-system/src/main/res/drawable/ic_radar.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/duckduckgo/app/browser/newtab/NewTabLegacyPageView.kt b/app/src/main/java/com/duckduckgo/app/browser/newtab/NewTabLegacyPageView.kt index a5995714db31..6d53b2852ff0 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/newtab/NewTabLegacyPageView.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/newtab/NewTabLegacyPageView.kt @@ -41,6 +41,7 @@ import com.duckduckgo.app.browser.newtab.NewTabLegacyPageViewModel.Command.Launc import com.duckduckgo.app.browser.newtab.NewTabLegacyPageViewModel.Command.LaunchScreen import com.duckduckgo.app.browser.newtab.NewTabLegacyPageViewModel.Command.SharePromoLinkRMF import com.duckduckgo.app.browser.newtab.NewTabLegacyPageViewModel.Command.SubmitUrl +import com.duckduckgo.app.browser.newtab.NewTabLegacyPageViewModel.Command.SubmitUrlInContext import com.duckduckgo.app.browser.newtab.NewTabLegacyPageViewModel.NewTabLegacyPageViewModelFactory import com.duckduckgo.app.browser.newtab.NewTabLegacyPageViewModel.NewTabLegacyPageViewModelProviderFactory import com.duckduckgo.app.browser.newtab.NewTabLegacyPageViewModel.ViewState @@ -169,6 +170,7 @@ class NewTabLegacyPageView @JvmOverloads constructor( is LaunchScreen -> launchScreen(command.screen, command.payload) is SharePromoLinkRMF -> launchSharePromoRMFPageChooser(command.url, command.shareTitle) is SubmitUrl -> submitUrl(command.url) + is SubmitUrlInContext -> submitUrlInContext(command.url) } } @@ -217,6 +219,9 @@ class NewTabLegacyPageView @JvmOverloads constructor( context.startActivity(browserNav.openInCurrentTab(context, url)) } + private fun submitUrlInContext(url: String) { + } + private fun showRemoteMessage( message: RemoteMessage, newMessage: Boolean, diff --git a/app/src/main/java/com/duckduckgo/app/browser/newtab/NewTabLegacyPageViewModel.kt b/app/src/main/java/com/duckduckgo/app/browser/newtab/NewTabLegacyPageViewModel.kt index 77a449495c0c..4f62182954a7 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/newtab/NewTabLegacyPageViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/newtab/NewTabLegacyPageViewModel.kt @@ -98,6 +98,7 @@ class NewTabLegacyPageViewModel @AssistedInject constructor( data object DismissMessage : Command() data class LaunchPlayStore(val appPackage: String) : Command() data class SubmitUrl(val url: String) : Command() + data class SubmitUrlInContext(val url: String) : Command() data object LaunchDefaultBrowser : Command() data object LaunchAppTPOnboarding : Command() data class SharePromoLinkRMF( diff --git a/app/src/main/java/com/duckduckgo/app/browser/remotemessage/CommandActionMapper.kt b/app/src/main/java/com/duckduckgo/app/browser/remotemessage/CommandActionMapper.kt index 677cbb652f22..4966ad03c75a 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/remotemessage/CommandActionMapper.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/remotemessage/CommandActionMapper.kt @@ -16,8 +16,6 @@ package com.duckduckgo.app.browser.remotemessage -import com.duckduckgo.app.browser.commands.Command -import com.duckduckgo.app.browser.commands.Command.* import com.duckduckgo.app.browser.newtab.NewTabLegacyPageViewModel import com.duckduckgo.di.scopes.ActivityScope import com.duckduckgo.remote.messaging.api.Action @@ -39,6 +37,7 @@ class RealCommandActionMapper @Inject constructor( is Dismiss -> NewTabLegacyPageViewModel.Command.DismissMessage is PlayStore -> NewTabLegacyPageViewModel.Command.LaunchPlayStore(action.value) is Url -> NewTabLegacyPageViewModel.Command.SubmitUrl(action.value) + is UrlInContext -> NewTabLegacyPageViewModel.Command.SubmitUrlInContext(action.value) is DefaultBrowser -> NewTabLegacyPageViewModel.Command.LaunchDefaultBrowser is AppTpOnboarding -> NewTabLegacyPageViewModel.Command.LaunchAppTPOnboarding is Share -> NewTabLegacyPageViewModel.Command.SharePromoLinkRMF(action.value, action.title) diff --git a/app/src/main/java/com/duckduckgo/app/browser/remotemessage/RemoteMessageMapper.kt b/app/src/main/java/com/duckduckgo/app/browser/remotemessage/RemoteMessageMapper.kt index 2e91c9ea268c..75495babd14a 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/remotemessage/RemoteMessageMapper.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/remotemessage/RemoteMessageMapper.kt @@ -21,6 +21,7 @@ import com.duckduckgo.common.ui.view.MessageCta.MessageType import com.duckduckgo.mobile.android.R import com.duckduckgo.remote.messaging.api.Content.BigSingleAction import com.duckduckgo.remote.messaging.api.Content.BigTwoActions +import com.duckduckgo.remote.messaging.api.Content.CardsList import com.duckduckgo.remote.messaging.api.Content.Medium import com.duckduckgo.remote.messaging.api.Content.Placeholder import com.duckduckgo.remote.messaging.api.Content.Placeholder.ANNOUNCE @@ -29,8 +30,11 @@ import com.duckduckgo.remote.messaging.api.Content.Placeholder.CRITICAL_UPDATE import com.duckduckgo.remote.messaging.api.Content.Placeholder.DDG_ANNOUNCE import com.duckduckgo.remote.messaging.api.Content.Placeholder.DUCK_AI import com.duckduckgo.remote.messaging.api.Content.Placeholder.DUCK_AI_OLD +import com.duckduckgo.remote.messaging.api.Content.Placeholder.IMAGE_AI +import com.duckduckgo.remote.messaging.api.Content.Placeholder.KEY_IMPORT import com.duckduckgo.remote.messaging.api.Content.Placeholder.MAC_AND_WINDOWS import com.duckduckgo.remote.messaging.api.Content.Placeholder.PRIVACY_SHIELD +import com.duckduckgo.remote.messaging.api.Content.Placeholder.RADAR import com.duckduckgo.remote.messaging.api.Content.Placeholder.VISUAL_DESIGN_UPDATE import com.duckduckgo.remote.messaging.api.Content.PromoSingleAction import com.duckduckgo.remote.messaging.api.Content.Small @@ -71,6 +75,12 @@ fun RemoteMessage.asMessage(isLightModeEnabled: Boolean): Message { promoAction = content.actionText, messageType = MessageType.REMOTE_PROMO_MESSAGE, ) + is CardsList -> Message( + title = content.titleText, + subtitle = content.descriptionText, + action = content.primaryActionText, + messageType = MessageType.REMOTE_LIST_MESSAGE, + ) } } @@ -89,5 +99,8 @@ private fun Placeholder.drawable(isLightModeEnabled: Boolean): Int { } else { R.drawable.ic_visual_design_update_artwork_dark } + IMAGE_AI -> R.drawable.ic_image_ai + RADAR -> R.drawable.ic_radar + KEY_IMPORT -> R.drawable.ic_key_import } } diff --git a/remote-messaging/remote-messaging-api/src/main/java/com/duckduckgo/remote/messaging/api/MessageActionMapperPlugin.kt b/remote-messaging/remote-messaging-api/src/main/java/com/duckduckgo/remote/messaging/api/MessageActionMapperPlugin.kt index 00aeafd73438..461738daf3d4 100644 --- a/remote-messaging/remote-messaging-api/src/main/java/com/duckduckgo/remote/messaging/api/MessageActionMapperPlugin.kt +++ b/remote-messaging/remote-messaging-api/src/main/java/com/duckduckgo/remote/messaging/api/MessageActionMapperPlugin.kt @@ -29,6 +29,7 @@ data class JsonMessageAction( @Suppress("ktlint:standard:class-naming") sealed class JsonActionType(val jsonValue: String) { data object URL : JsonActionType("url") + data object URL_IN_CONTEXT : JsonActionType("url_in_context") data object PLAYSTORE : JsonActionType("playstore") data object DEFAULT_BROWSER : JsonActionType("defaultBrowser") data object DISMISS : JsonActionType("dismiss") diff --git a/remote-messaging/remote-messaging-api/src/main/java/com/duckduckgo/remote/messaging/api/RemoteMessage.kt b/remote-messaging/remote-messaging-api/src/main/java/com/duckduckgo/remote/messaging/api/RemoteMessage.kt index f5a5e784e56d..450cbf088d3d 100644 --- a/remote-messaging/remote-messaging-api/src/main/java/com/duckduckgo/remote/messaging/api/RemoteMessage.kt +++ b/remote-messaging/remote-messaging-api/src/main/java/com/duckduckgo/remote/messaging/api/RemoteMessage.kt @@ -23,6 +23,7 @@ import com.duckduckgo.remote.messaging.api.Content.MessageType.BIG_TWO_ACTION import com.duckduckgo.remote.messaging.api.Content.MessageType.MEDIUM import com.duckduckgo.remote.messaging.api.Content.MessageType.PROMO_SINGLE_ACTION import com.duckduckgo.remote.messaging.api.Content.MessageType.SMALL +import com.duckduckgo.remote.messaging.api.Content.Placeholder import com.duckduckgo.remote.messaging.api.JsonActionType.APP_TP_ONBOARDING import com.duckduckgo.remote.messaging.api.JsonActionType.DEFAULT_BROWSER import com.duckduckgo.remote.messaging.api.JsonActionType.DISMISS @@ -73,12 +74,22 @@ sealed class Content(val messageType: MessageType) { val action: Action, ) : Content(PROMO_SINGLE_ACTION) + data class CardsList( + val titleText: String, + val descriptionText: String, + val placeholder: Placeholder, + val primaryActionText: String, + val primaryAction: Action, + val listItems: List, + ) : Content(MessageType.CARDS_LIST) + enum class MessageType { SMALL, MEDIUM, BIG_SINGLE_ACTION, BIG_TWO_ACTION, PROMO_SINGLE_ACTION, + CARDS_LIST, } enum class Placeholder(val jsonValue: String) { @@ -91,6 +102,9 @@ sealed class Content(val messageType: MessageType) { DUCK_AI_OLD("Duck.ai"), DUCK_AI("DuckAi"), VISUAL_DESIGN_UPDATE("VisualDesignUpdate"), + IMAGE_AI("ImageAI"), + RADAR("Radar"), + KEY_IMPORT("KeyImport"), ; companion object { @@ -103,6 +117,7 @@ sealed class Content(val messageType: MessageType) { sealed class Action(val actionType: String, open val value: String, open val additionalParameters: Map?) { data class Url(override val value: String) : Action(URL.jsonValue, value, null) + data class UrlInContext(override val value: String) : Action(JsonActionType.URL_IN_CONTEXT.jsonValue, value, null) data class PlayStore(override val value: String) : Action(PLAYSTORE.jsonValue, value, null) data object DefaultBrowser : Action(DEFAULT_BROWSER.jsonValue, "", null) data object Dismiss : Action(DISMISS.jsonValue, "", null) @@ -128,3 +143,16 @@ sealed class Action(val actionType: String, open val value: String, open val add override val additionalParameters: Map?, ) : Action(JsonActionType.SURVEY.jsonValue, value, additionalParameters) } + +data class CardItem( + val id: String, + val type: CardItemType, + val titleText: String, + val descriptionText: String, + val placeholder: Placeholder, + val primaryAction: Action, +) + +enum class CardItemType(val jsonValue: String) { + TWO_LINE_LIST_ITEM("two_line_list_item"), +} diff --git a/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/mappers/JsonActionMappers.kt b/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/mappers/JsonActionMappers.kt index cea501a61076..cccf1bd9162f 100644 --- a/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/mappers/JsonActionMappers.kt +++ b/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/mappers/JsonActionMappers.kt @@ -21,6 +21,7 @@ import com.duckduckgo.di.scopes.AppScope import com.duckduckgo.navigation.api.GlobalActivityStarter import com.duckduckgo.navigation.api.GlobalActivityStarter.DeeplinkActivityParams import com.duckduckgo.remote.messaging.api.Action +import com.duckduckgo.remote.messaging.api.JsonActionType import com.duckduckgo.remote.messaging.api.JsonActionType.DEFAULT_BROWSER import com.duckduckgo.remote.messaging.api.JsonActionType.DISMISS import com.duckduckgo.remote.messaging.api.JsonActionType.NAVIGATION @@ -45,6 +46,19 @@ class UrlActionMapper @Inject constructor() : MessageActionMapperPlugin { } } +@ContributesMultibinding( + AppScope::class, +) +class UrlInContextActionMapper @Inject constructor() : MessageActionMapperPlugin { + override fun evaluate(jsonMessageAction: JsonMessageAction): Action? { + return if (jsonMessageAction.type == JsonActionType.URL_IN_CONTEXT.jsonValue) { + Action.UrlInContext(jsonMessageAction.value) + } else { + null + } + } +} + @ContributesMultibinding( AppScope::class, ) diff --git a/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/mappers/JsonRemoteMessageMapper.kt b/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/mappers/JsonRemoteMessageMapper.kt index 08a08bdb95b3..79cd90fedcf9 100644 --- a/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/mappers/JsonRemoteMessageMapper.kt +++ b/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/mappers/JsonRemoteMessageMapper.kt @@ -17,9 +17,12 @@ package com.duckduckgo.remote.messaging.impl.mappers import com.duckduckgo.remote.messaging.api.Action +import com.duckduckgo.remote.messaging.api.CardItem +import com.duckduckgo.remote.messaging.api.CardItemType import com.duckduckgo.remote.messaging.api.Content import com.duckduckgo.remote.messaging.api.Content.BigSingleAction import com.duckduckgo.remote.messaging.api.Content.BigTwoActions +import com.duckduckgo.remote.messaging.api.Content.CardsList import com.duckduckgo.remote.messaging.api.Content.Medium import com.duckduckgo.remote.messaging.api.Content.Placeholder import com.duckduckgo.remote.messaging.api.Content.PromoSingleAction @@ -31,6 +34,7 @@ import com.duckduckgo.remote.messaging.api.Surface import com.duckduckgo.remote.messaging.impl.models.* import com.duckduckgo.remote.messaging.impl.models.JsonMessageType.BIG_SINGLE_ACTION import com.duckduckgo.remote.messaging.impl.models.JsonMessageType.BIG_TWO_ACTION +import com.duckduckgo.remote.messaging.impl.models.JsonMessageType.CARDS_LIST import com.duckduckgo.remote.messaging.impl.models.JsonMessageType.MEDIUM import com.duckduckgo.remote.messaging.impl.models.JsonMessageType.PROMO_SINGLE_ACTION import com.duckduckgo.remote.messaging.impl.models.JsonMessageType.SMALL @@ -86,6 +90,17 @@ private val promoSingleActionMapper: (JsonContent, Set) -> Content = { jsonContent, actionMappers -> + CardsList( + titleText = jsonContent.titleText.failIfEmpty(), + descriptionText = jsonContent.descriptionText.failIfEmpty(), + placeholder = jsonContent.placeholder.asPlaceholder(), + primaryActionText = jsonContent.primaryActionText.failIfEmpty(), + primaryAction = jsonContent.primaryAction!!.toAction(actionMappers), + listItems = jsonContent.listItems.toListItems(actionMappers), + ) +} + // plugin point? private val messageMappers = mapOf( Pair(SMALL.jsonValue, smallMapper), @@ -93,6 +108,7 @@ private val messageMappers = mapOf( Pair(BIG_SINGLE_ACTION.jsonValue, bigMessageSingleActionMapper), Pair(BIG_TWO_ACTION.jsonValue, bigMessageTwoActionMapper), Pair(PROMO_SINGLE_ACTION.jsonValue, promoSingleActionMapper), + Pair(CARDS_LIST.jsonValue, cardsListMapper), ) fun List.mapToRemoteMessage( @@ -114,7 +130,7 @@ private fun JsonRemoteMessage.map( ) remoteMessage.localizeMessage(this.translations, locale) }.onFailure { - logcat(ERROR) { "RMF: error $it" } + logcat(ERROR) { "RMF: error parsing message id=${this.id}: ${it.message}\n${it.stackTraceToString()}" } }.getOrNull() } @@ -147,14 +163,32 @@ private fun JsonMessageAction.toAction(actionMappers: Set?.toListItems(actionMappers: Set): List { + return this?.map { jsonItem -> + CardItem( + id = jsonItem.id.failIfEmpty(), + type = jsonItem.type.toCardItemType(), + titleText = jsonItem.titleText.failIfEmpty(), + descriptionText = jsonItem.descriptionText.failIfEmpty(), + placeholder = jsonItem.placeholder.asPlaceholder(), + primaryAction = jsonItem.primaryAction?.toAction(actionMappers) + ?: throw IllegalStateException("CardItem primaryAction cannot be null"), + ) + } ?: emptyList() +} + private fun Content.localize(translations: JsonContentTranslations): Content { return when (this) { is BigSingleAction -> this.copy( @@ -181,5 +215,10 @@ private fun Content.localize(translations: JsonContentTranslations): Content { descriptionText = translations.descriptionText.takeUnless { it.isEmpty() } ?: this.descriptionText, actionText = translations.actionText.takeUnless { it.isEmpty() } ?: this.actionText, ) + is CardsList -> this.copy( + titleText = translations.titleText.takeUnless { it.isEmpty() } ?: this.titleText, + descriptionText = translations.descriptionText.takeUnless { it.isEmpty() } ?: this.descriptionText, + primaryActionText = translations.primaryActionText.takeUnless { it.isEmpty() } ?: this.primaryActionText, + ) } } diff --git a/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/mappers/RemoteMessageMapper.kt b/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/mappers/RemoteMessageMapper.kt index b4e87eadb01c..f410488cbb20 100644 --- a/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/mappers/RemoteMessageMapper.kt +++ b/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/mappers/RemoteMessageMapper.kt @@ -21,6 +21,7 @@ import com.duckduckgo.common.ui.view.MessageCta.MessageType import com.duckduckgo.mobile.android.R import com.duckduckgo.remote.messaging.api.Content.BigSingleAction import com.duckduckgo.remote.messaging.api.Content.BigTwoActions +import com.duckduckgo.remote.messaging.api.Content.CardsList import com.duckduckgo.remote.messaging.api.Content.Medium import com.duckduckgo.remote.messaging.api.Content.Placeholder import com.duckduckgo.remote.messaging.api.Content.Placeholder.ANNOUNCE @@ -29,8 +30,11 @@ import com.duckduckgo.remote.messaging.api.Content.Placeholder.CRITICAL_UPDATE import com.duckduckgo.remote.messaging.api.Content.Placeholder.DDG_ANNOUNCE import com.duckduckgo.remote.messaging.api.Content.Placeholder.DUCK_AI import com.duckduckgo.remote.messaging.api.Content.Placeholder.DUCK_AI_OLD +import com.duckduckgo.remote.messaging.api.Content.Placeholder.IMAGE_AI +import com.duckduckgo.remote.messaging.api.Content.Placeholder.KEY_IMPORT import com.duckduckgo.remote.messaging.api.Content.Placeholder.MAC_AND_WINDOWS import com.duckduckgo.remote.messaging.api.Content.Placeholder.PRIVACY_SHIELD +import com.duckduckgo.remote.messaging.api.Content.Placeholder.RADAR import com.duckduckgo.remote.messaging.api.Content.Placeholder.VISUAL_DESIGN_UPDATE import com.duckduckgo.remote.messaging.api.Content.PromoSingleAction import com.duckduckgo.remote.messaging.api.Content.Small @@ -71,6 +75,12 @@ fun RemoteMessage.asMessage(isLightModeEnabled: Boolean): Message { promoAction = content.actionText, messageType = MessageType.REMOTE_PROMO_MESSAGE, ) + is CardsList -> Message( + title = content.titleText, + subtitle = content.descriptionText, + action = content.primaryActionText, + messageType = MessageType.REMOTE_LIST_MESSAGE, + ) } } @@ -89,5 +99,8 @@ private fun Placeholder.drawable(isLightModeEnabled: Boolean): Int { } else { R.drawable.ic_visual_design_update_artwork_dark } + IMAGE_AI -> R.drawable.ic_image_ai + RADAR -> R.drawable.ic_radar + KEY_IMPORT -> R.drawable.ic_key_import } } diff --git a/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/models/JsonRemoteMessagingConfig.kt b/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/models/JsonRemoteMessagingConfig.kt index 689445046a35..aa56900fc609 100644 --- a/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/models/JsonRemoteMessagingConfig.kt +++ b/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/models/JsonRemoteMessagingConfig.kt @@ -45,6 +45,7 @@ data class JsonContent( val secondaryAction: JsonMessageAction? = null, val actionText: String = "", val action: JsonMessageAction? = null, + val listItems: List? = null, ) data class JsonContentTranslations( @@ -66,6 +67,15 @@ data class JsonTargetPercentile( val before: Float?, ) +data class JsonListItem( + val id: String, + val type: String, + val titleText: String, + val descriptionText: String, + val placeholder: String = "", + val primaryAction: JsonMessageAction?, +) + @Suppress("ktlint:standard:class-naming") sealed class JsonMessageType(val jsonValue: String) { data object SMALL : JsonMessageType("small") @@ -73,4 +83,5 @@ sealed class JsonMessageType(val jsonValue: String) { data object BIG_SINGLE_ACTION : JsonMessageType("big_single_action") data object BIG_TWO_ACTION : JsonMessageType("big_two_action") data object PROMO_SINGLE_ACTION : JsonMessageType("promo_single_action") + data object CARDS_LIST : JsonMessageType("cards_list") } diff --git a/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/newtab/RemoteMessageView.kt b/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/newtab/RemoteMessageView.kt index dbd188260f16..ccba7ca20d51 100644 --- a/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/newtab/RemoteMessageView.kt +++ b/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/newtab/RemoteMessageView.kt @@ -140,6 +140,7 @@ class RemoteMessageView @JvmOverloads constructor( is LaunchScreen -> launchScreen(command.screen, command.payload) is SharePromoLinkRMF -> launchSharePromoRMFPageChooser(command.url, command.shareTitle) is SubmitUrl -> submitUrl(command.url) + is Command.SubmitUrlInContext -> submitUrlInContext(command.url) } } @@ -221,6 +222,9 @@ class RemoteMessageView @JvmOverloads constructor( private fun submitUrl(url: String) { context.startActivity(browserNav.openInCurrentTab(context, url)) } + + private fun submitUrlInContext(url: String) { + } } @ContributesActivePlugin( diff --git a/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/newtab/RemoteMessageViewModel.kt b/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/newtab/RemoteMessageViewModel.kt index cb2427bc5061..52fbcb1fd1cb 100644 --- a/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/newtab/RemoteMessageViewModel.kt +++ b/remote-messaging/remote-messaging-impl/src/main/java/com/duckduckgo/remote/messaging/impl/newtab/RemoteMessageViewModel.kt @@ -34,6 +34,7 @@ import com.duckduckgo.remote.messaging.api.Action.PlayStore import com.duckduckgo.remote.messaging.api.Action.Share import com.duckduckgo.remote.messaging.api.Action.Survey import com.duckduckgo.remote.messaging.api.Action.Url +import com.duckduckgo.remote.messaging.api.Action.UrlInContext import com.duckduckgo.remote.messaging.api.RemoteMessage import com.duckduckgo.remote.messaging.api.RemoteMessageModel import com.duckduckgo.survey.api.SurveyParameterManager @@ -68,6 +69,7 @@ class RemoteMessageViewModel @Inject constructor( data object DismissMessage : Command() data class LaunchPlayStore(val appPackage: String) : Command() data class SubmitUrl(val url: String) : Command() + data class SubmitUrlInContext(val url: String) : Command() data object LaunchDefaultBrowser : Command() data object LaunchAppTPOnboarding : Command() data class SharePromoLinkRMF( @@ -162,6 +164,7 @@ class RemoteMessageViewModel @Inject constructor( is Dismiss -> Command.DismissMessage is PlayStore -> Command.LaunchPlayStore(this.value) is Url -> Command.SubmitUrl(this.value) + is UrlInContext -> Command.SubmitUrlInContext(this.value) is DefaultBrowser -> Command.LaunchDefaultBrowser is AppTpOnboarding -> Command.LaunchAppTPOnboarding is Share -> Command.SharePromoLinkRMF(this.value, this.title) diff --git a/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/fixtures/FakeActionPlugins.kt b/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/fixtures/FakeActionPlugins.kt index 06e7d490b9da..fa3e8624c294 100644 --- a/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/fixtures/FakeActionPlugins.kt +++ b/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/fixtures/FakeActionPlugins.kt @@ -21,9 +21,11 @@ import com.duckduckgo.remote.messaging.impl.mappers.DismissActionMapper import com.duckduckgo.remote.messaging.impl.mappers.PlayStoreActionMapper import com.duckduckgo.remote.messaging.impl.mappers.ShareActionMapper import com.duckduckgo.remote.messaging.impl.mappers.UrlActionMapper +import com.duckduckgo.remote.messaging.impl.mappers.UrlInContextActionMapper val messageActionPlugins = listOf( UrlActionMapper(), + UrlInContextActionMapper(), DismissActionMapper(), PlayStoreActionMapper(), DefaultBrowserActionMapper(), diff --git a/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/fixtures/JsonRemoteMessageOM.kt b/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/fixtures/JsonRemoteMessageOM.kt index 0026ca328041..7548cab08493 100644 --- a/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/fixtures/JsonRemoteMessageOM.kt +++ b/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/fixtures/JsonRemoteMessageOM.kt @@ -19,6 +19,7 @@ package com.duckduckgo.remote.messaging.fixtures import com.duckduckgo.remote.messaging.api.JsonMessageAction import com.duckduckgo.remote.messaging.impl.models.JsonContent import com.duckduckgo.remote.messaging.impl.models.JsonContentTranslations +import com.duckduckgo.remote.messaging.impl.models.JsonListItem import com.duckduckgo.remote.messaging.impl.models.JsonMatchingRule import com.duckduckgo.remote.messaging.impl.models.JsonRemoteMessage import com.duckduckgo.remote.messaging.impl.models.JsonRemoteMessagingConfig @@ -103,6 +104,40 @@ object JsonRemoteMessageOM { action = action, ) + fun cardsListJsonContent( + titleText: String = "title", + descriptionText: String = "description", + placeholder: String = "Announce", + primaryActionText: String = "Action", + primaryAction: JsonMessageAction = jsonMessageAction(), + listItems: List = listOf( + JsonListItem( + id = "item1", + type = "two_line_list_item", + titleText = "Item Title 1", + descriptionText = "Item Description 1", + placeholder = "ImageAI", + primaryAction = jsonMessageAction(), + ), + JsonListItem( + id = "item2", + type = "two_line_list_item", + titleText = "Item Title 2", + descriptionText = "Item Description 2", + placeholder = "Radar", + primaryAction = jsonMessageAction(), + ), + ), + ) = JsonContent( + messageType = "cards_list", + titleText = titleText, + descriptionText = descriptionText, + placeholder = placeholder, + primaryActionText = primaryActionText, + primaryAction = primaryAction, + listItems = listItems, + ) + fun emptyJsonContent(messageType: String = "") = JsonContent(messageType = messageType) fun aJsonMessage( diff --git a/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/fixtures/RemoteMessageOM.kt b/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/fixtures/RemoteMessageOM.kt index cf21c1b03c38..755fee195857 100644 --- a/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/fixtures/RemoteMessageOM.kt +++ b/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/fixtures/RemoteMessageOM.kt @@ -17,10 +17,14 @@ package com.duckduckgo.remote.messaging.fixtures import com.duckduckgo.remote.messaging.api.Action +import com.duckduckgo.remote.messaging.api.CardItem +import com.duckduckgo.remote.messaging.api.CardItemType import com.duckduckgo.remote.messaging.api.Content import com.duckduckgo.remote.messaging.api.Content.Placeholder import com.duckduckgo.remote.messaging.api.Content.Placeholder.ANNOUNCE +import com.duckduckgo.remote.messaging.api.Content.Placeholder.IMAGE_AI import com.duckduckgo.remote.messaging.api.Content.Placeholder.MAC_AND_WINDOWS +import com.duckduckgo.remote.messaging.api.Content.Placeholder.RADAR import com.duckduckgo.remote.messaging.api.RemoteMessage import com.duckduckgo.remote.messaging.api.Surface @@ -94,6 +98,39 @@ object RemoteMessageOM { action = action, ) + fun cardsListContent( + titleText: String = "title", + descriptionText: String = "description", + placeholder: Placeholder = ANNOUNCE, + primaryActionText: String = "Action", + primaryAction: Action = urlAction(), + listItems: List = listOf( + CardItem( + id = "item1", + type = CardItemType.TWO_LINE_LIST_ITEM, + titleText = "Item Title 1", + descriptionText = "Item Description 1", + placeholder = IMAGE_AI, + primaryAction = urlAction(), + ), + CardItem( + id = "item2", + type = CardItemType.TWO_LINE_LIST_ITEM, + titleText = "Item Title 2", + descriptionText = "Item Description 2", + placeholder = RADAR, + primaryAction = urlAction(), + ), + ), + ) = Content.CardsList( + titleText = titleText, + descriptionText = descriptionText, + placeholder = placeholder, + primaryActionText = primaryActionText, + primaryAction = primaryAction, + listItems = listItems, + ) + fun aSmallMessage( id: String = "id", content: Content = smallContent(), @@ -173,4 +210,20 @@ object RemoteMessageOM { surfaces = surfaces, ) } + + fun aCardsListMessage( + id: String = "id", + content: Content = cardsListContent(), + exclusionRules: List = emptyList(), + matchingRules: List = emptyList(), + surfaces: List = emptyList(), + ): RemoteMessage { + return RemoteMessage( + id = id, + content = content, + exclusionRules = exclusionRules, + matchingRules = matchingRules, + surfaces = surfaces, + ) + } } diff --git a/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/impl/CardsListMessageMapperTest.kt b/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/impl/CardsListMessageMapperTest.kt new file mode 100644 index 000000000000..a1ace46ff4e2 --- /dev/null +++ b/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/impl/CardsListMessageMapperTest.kt @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.remote.messaging.impl + +import com.duckduckgo.remote.messaging.api.Action +import com.duckduckgo.remote.messaging.api.CardItemType +import com.duckduckgo.remote.messaging.api.Content +import com.duckduckgo.remote.messaging.api.JsonMessageAction +import com.duckduckgo.remote.messaging.fixtures.JsonRemoteMessageOM.aJsonMessage +import com.duckduckgo.remote.messaging.fixtures.JsonRemoteMessageOM.cardsListJsonContent +import com.duckduckgo.remote.messaging.fixtures.messageActionPlugins +import com.duckduckgo.remote.messaging.impl.mappers.mapToRemoteMessage +import com.duckduckgo.remote.messaging.impl.models.JsonContent +import com.duckduckgo.remote.messaging.impl.models.JsonListItem +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Test +import java.util.* + +class CardsListMessageMapperTest { + + @Test + fun whenCardsListMessageWithValidDataThenReturnMessage() { + val jsonMessages = listOf( + aJsonMessage(id = "cards1", content = cardsListJsonContent()), + ) + + val remoteMessages = jsonMessages.mapToRemoteMessage(Locale.US, messageActionPlugins) + + assertEquals(1, remoteMessages.size) + val message = remoteMessages.first() + assertEquals("cards1", message.id) + assertTrue(message.content is Content.CardsList) + + val content = message.content as Content.CardsList + assertEquals("title", content.titleText) + assertEquals("description", content.descriptionText) + assertEquals("Action", content.primaryActionText) + assertEquals(2, content.listItems.size) + } + + @Test + fun whenCardsListMessageWithCustomListItemsThenMapCorrectly() { + val customListItems = listOf( + JsonListItem( + id = "feature1", + type = "two_line_list_item", + titleText = "Feature One", + descriptionText = "Description for feature one", + placeholder = "ImageAI", + primaryAction = JsonMessageAction(type = "url", value = "https://example.com/feature1", additionalParameters = null), + ), + JsonListItem( + id = "feature2", + type = "two_line_list_item", + titleText = "Feature Two", + descriptionText = "Description for feature two", + placeholder = "Radar", + primaryAction = JsonMessageAction(type = "url", value = "https://example.com/feature2", additionalParameters = null), + ), + JsonListItem( + id = "feature3", + type = "two_line_list_item", + titleText = "Feature Three", + descriptionText = "Description for feature three", + placeholder = "KeyImport", + primaryAction = JsonMessageAction(type = "url", value = "https://example.com/feature3", additionalParameters = null), + ), + ) + + val jsonMessages = listOf( + aJsonMessage( + id = "cards2", + content = cardsListJsonContent(listItems = customListItems), + ), + ) + + val remoteMessages = jsonMessages.mapToRemoteMessage(Locale.US, messageActionPlugins) + + assertEquals(1, remoteMessages.size) + val content = remoteMessages.first().content as Content.CardsList + assertEquals(3, content.listItems.size) + + val item1 = content.listItems[0] + assertEquals("feature1", item1.id) + assertEquals(CardItemType.TWO_LINE_LIST_ITEM, item1.type) + assertEquals("Feature One", item1.titleText) + assertEquals("Description for feature one", item1.descriptionText) + assertEquals(Content.Placeholder.IMAGE_AI, item1.placeholder) + assertTrue(item1.primaryAction is Action.Url) + assertEquals("https://example.com/feature1", (item1.primaryAction as Action.Url).value) + + val item2 = content.listItems[1] + assertEquals("feature2", item2.id) + assertEquals("Feature Two", item2.titleText) + assertEquals(Content.Placeholder.RADAR, item2.placeholder) + + val item3 = content.listItems[2] + assertEquals("feature3", item3.id) + assertEquals("Feature Three", item3.titleText) + assertEquals(Content.Placeholder.KEY_IMPORT, item3.placeholder) + } + + @Test + fun whenCardsListMessageWithEmptyListItemsThenReturnEmptyList() { + val jsonMessages = listOf( + aJsonMessage( + id = "cards3", + content = cardsListJsonContent(listItems = emptyList()), + ), + ) + + val remoteMessages = jsonMessages.mapToRemoteMessage(Locale.US, messageActionPlugins) + + assertEquals(1, remoteMessages.size) + val content = remoteMessages.first().content as Content.CardsList + assertEquals(0, content.listItems.size) + } + + @Test + fun whenCardsListMessageWithNullListItemsThenReturnEmptyList() { + val jsonContent = JsonContent( + messageType = "cards_list", + titleText = "title", + descriptionText = "description", + placeholder = "Announce", + primaryActionText = "Action", + primaryAction = JsonMessageAction(type = "url", value = "https://example.com", additionalParameters = null), + listItems = null, + ) + + val jsonMessages = listOf( + aJsonMessage(id = "cards4", content = jsonContent), + ) + + val remoteMessages = jsonMessages.mapToRemoteMessage(Locale.US, messageActionPlugins) + + assertEquals(1, remoteMessages.size) + val content = remoteMessages.first().content as Content.CardsList + assertNotNull(content.listItems) + assertEquals(0, content.listItems.size) + } + + @Test + fun whenCardsListMessageWithMissingRequiredFieldsThenMessageIsFiltered() { + // Missing title + val jsonContentMissingTitle = JsonContent( + messageType = "cards_list", + titleText = "", + descriptionText = "description", + placeholder = "Announce", + primaryActionText = "Action", + primaryAction = JsonMessageAction(type = "url", value = "https://example.com", additionalParameters = null), + listItems = emptyList(), + ) + + val jsonMessages = listOf( + aJsonMessage(id = "cards5", content = jsonContentMissingTitle), + ) + + val remoteMessages = jsonMessages.mapToRemoteMessage(Locale.US, messageActionPlugins) + + // Message should be filtered out due to empty required field + assertEquals(0, remoteMessages.size) + } + + @Test + fun whenCardsListMessageWithListItemMissingRequiredFieldsThenMessageIsFiltered() { + val invalidListItems = listOf( + JsonListItem( + id = "", // Empty ID should cause failure + type = "two_line_list_item", + titleText = "Feature", + descriptionText = "Description", + placeholder = "ImageAI", + primaryAction = JsonMessageAction(type = "url", value = "https://example.com", additionalParameters = null), + ), + ) + + val jsonMessages = listOf( + aJsonMessage( + id = "cards6", + content = cardsListJsonContent(listItems = invalidListItems), + ), + ) + + val remoteMessages = jsonMessages.mapToRemoteMessage(Locale.US, messageActionPlugins) + + // Message should be filtered out due to invalid list item + assertEquals(0, remoteMessages.size) + } + + @Test + fun whenCardsListMessageWithListItemMissingActionThenMessageIsFiltered() { + val listItemsWithNullAction = listOf( + JsonListItem( + id = "item1", + type = "two_line_list_item", + titleText = "Feature", + descriptionText = "Description", + placeholder = "ImageAI", + primaryAction = null, // Null action should cause failure + ), + ) + + val jsonMessages = listOf( + aJsonMessage( + id = "cards7", + content = cardsListJsonContent(listItems = listItemsWithNullAction), + ), + ) + + val remoteMessages = jsonMessages.mapToRemoteMessage(Locale.US, messageActionPlugins) + + // Message should be filtered out due to null action in list item + assertEquals(0, remoteMessages.size) + } + + @Test + fun whenCardsListMessageWithDifferentPlaceholdersThenMapCorrectly() { + val listItemsWithDifferentPlaceholders = listOf( + JsonListItem( + id = "item1", + type = "two_line_list_item", + titleText = "AI Feature", + descriptionText = "AI Description", + placeholder = "DuckAi", + primaryAction = JsonMessageAction(type = "url", value = "https://example.com", additionalParameters = null), + ), + JsonListItem( + id = "item2", + type = "two_line_list_item", + titleText = "Privacy Feature", + descriptionText = "Privacy Description", + placeholder = "PrivacyShield", + primaryAction = JsonMessageAction(type = "url", value = "https://example.com", additionalParameters = null), + ), + ) + + val jsonMessages = listOf( + aJsonMessage( + id = "cards8", + content = cardsListJsonContent( + placeholder = "DDGAnnounce", + listItems = listItemsWithDifferentPlaceholders, + ), + ), + ) + + val remoteMessages = jsonMessages.mapToRemoteMessage(Locale.US, messageActionPlugins) + + assertEquals(1, remoteMessages.size) + val content = remoteMessages.first().content as Content.CardsList + assertEquals(Content.Placeholder.DDG_ANNOUNCE, content.placeholder) + assertEquals(Content.Placeholder.DUCK_AI, content.listItems[0].placeholder) + assertEquals(Content.Placeholder.PRIVACY_SHIELD, content.listItems[1].placeholder) + } + + @Test + fun whenCardsListMessageWithDifferentActionTypesThenMapCorrectly() { + val listItemsWithDifferentActions = listOf( + JsonListItem( + id = "item1", + type = "two_line_list_item", + titleText = "Web Feature", + descriptionText = "Opens URL", + placeholder = "ImageAI", + primaryAction = JsonMessageAction(type = "url", value = "https://example.com", additionalParameters = null), + ), + JsonListItem( + id = "item2", + type = "two_line_list_item", + titleText = "Dismiss Feature", + descriptionText = "Just dismisses", + placeholder = "Radar", + primaryAction = JsonMessageAction(type = "dismiss", value = "", additionalParameters = null), + ), + JsonListItem( + id = "item3", + type = "two_line_list_item", + titleText = "Share Feature", + descriptionText = "Share content", + placeholder = "KeyImport", + primaryAction = JsonMessageAction( + type = "share", + value = "Share this!", + additionalParameters = mapOf("title" to "Share Title"), + ), + ), + ) + + val jsonMessages = listOf( + aJsonMessage( + id = "cards9", + content = cardsListJsonContent(listItems = listItemsWithDifferentActions), + ), + ) + + val remoteMessages = jsonMessages.mapToRemoteMessage(Locale.US, messageActionPlugins) + + assertEquals(1, remoteMessages.size) + val content = remoteMessages.first().content as Content.CardsList + assertEquals(3, content.listItems.size) + + assertTrue(content.listItems[0].primaryAction is Action.Url) + assertTrue(content.listItems[1].primaryAction is Action.Dismiss) + assertTrue(content.listItems[2].primaryAction is Action.Share) + + val shareAction = content.listItems[2].primaryAction as Action.Share + assertEquals("Share this!", shareAction.value) + assertEquals("Share Title", shareAction.title) + } +} diff --git a/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/impl/JsonRemoteMessageMapperTest.kt b/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/impl/JsonRemoteMessageMapperTest.kt index 9eaae30cbdd2..5978868215ca 100644 --- a/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/impl/JsonRemoteMessageMapperTest.kt +++ b/remote-messaging/remote-messaging-impl/src/test/java/com/duckduckgo/remote/messaging/impl/JsonRemoteMessageMapperTest.kt @@ -21,17 +21,20 @@ import com.duckduckgo.remote.messaging.api.Surface.NEW_TAB_PAGE import com.duckduckgo.remote.messaging.fixtures.JsonRemoteMessageOM.aJsonMessage import com.duckduckgo.remote.messaging.fixtures.JsonRemoteMessageOM.bigSingleActionJsonContent import com.duckduckgo.remote.messaging.fixtures.JsonRemoteMessageOM.bigTwoActionJsonContent +import com.duckduckgo.remote.messaging.fixtures.JsonRemoteMessageOM.cardsListJsonContent import com.duckduckgo.remote.messaging.fixtures.JsonRemoteMessageOM.emptyJsonContent import com.duckduckgo.remote.messaging.fixtures.JsonRemoteMessageOM.mediumJsonContent import com.duckduckgo.remote.messaging.fixtures.JsonRemoteMessageOM.promoSingleActionJsonContent import com.duckduckgo.remote.messaging.fixtures.JsonRemoteMessageOM.smallJsonContent import com.duckduckgo.remote.messaging.fixtures.RemoteMessageOM.aBigSingleActionMessage import com.duckduckgo.remote.messaging.fixtures.RemoteMessageOM.aBigTwoActionsMessage +import com.duckduckgo.remote.messaging.fixtures.RemoteMessageOM.aCardsListMessage import com.duckduckgo.remote.messaging.fixtures.RemoteMessageOM.aMediumMessage import com.duckduckgo.remote.messaging.fixtures.RemoteMessageOM.aPromoSingleActionMessage import com.duckduckgo.remote.messaging.fixtures.RemoteMessageOM.aSmallMessage import com.duckduckgo.remote.messaging.fixtures.RemoteMessageOM.bigSingleActionContent import com.duckduckgo.remote.messaging.fixtures.RemoteMessageOM.bigTwoActionsContent +import com.duckduckgo.remote.messaging.fixtures.RemoteMessageOM.cardsListContent import com.duckduckgo.remote.messaging.fixtures.RemoteMessageOM.mediumContent import com.duckduckgo.remote.messaging.fixtures.RemoteMessageOM.promoSingleActionContent import com.duckduckgo.remote.messaging.fixtures.RemoteMessageOM.smallContent @@ -66,6 +69,7 @@ class JsonRemoteMessageMapperTest(private val testCase: TestCase) { aJsonMessage(id = "id3", content = bigSingleActionJsonContent()), aJsonMessage(id = "id4", content = bigTwoActionJsonContent()), aJsonMessage(id = "id5", content = promoSingleActionJsonContent()), + aJsonMessage(id = "id6", content = cardsListJsonContent()), ), listOf( aSmallMessage(id = "id1", surfaces = listOf(NEW_TAB_PAGE)), @@ -73,6 +77,7 @@ class JsonRemoteMessageMapperTest(private val testCase: TestCase) { aBigSingleActionMessage(id = "id3", surfaces = listOf(NEW_TAB_PAGE)), aBigTwoActionsMessage(id = "id4", surfaces = listOf(NEW_TAB_PAGE)), aPromoSingleActionMessage(id = "id5", surfaces = listOf(NEW_TAB_PAGE)), + aCardsListMessage(id = "id6", surfaces = listOf(NEW_TAB_PAGE)), ), ), TestCase( @@ -83,6 +88,7 @@ class JsonRemoteMessageMapperTest(private val testCase: TestCase) { aJsonMessage(id = "id4", content = bigSingleActionJsonContent()), aJsonMessage(id = "id5", content = bigTwoActionJsonContent()), aJsonMessage(id = "id6", content = promoSingleActionJsonContent()), + aJsonMessage(id = "id7", content = cardsListJsonContent()), ), listOf( aSmallMessage(id = "id2", surfaces = listOf(NEW_TAB_PAGE)), @@ -90,6 +96,7 @@ class JsonRemoteMessageMapperTest(private val testCase: TestCase) { aBigSingleActionMessage(id = "id4", surfaces = listOf(NEW_TAB_PAGE)), aBigTwoActionsMessage(id = "id5", surfaces = listOf(NEW_TAB_PAGE)), aPromoSingleActionMessage(id = "id6", surfaces = listOf(NEW_TAB_PAGE)), + aCardsListMessage(id = "id7", surfaces = listOf(NEW_TAB_PAGE)), ), ), TestCase( @@ -135,6 +142,11 @@ class JsonRemoteMessageMapperTest(private val testCase: TestCase) { content = promoSingleActionJsonContent(), translations = mapOf("fr" to frenchTranslations()), ), + aJsonMessage( + id = "id6", + content = cardsListJsonContent(), + translations = mapOf("fr" to frenchTranslations()), + ), ), listOf( aSmallMessage( @@ -178,6 +190,15 @@ class JsonRemoteMessageMapperTest(private val testCase: TestCase) { ), surfaces = listOf(NEW_TAB_PAGE), ), + aCardsListMessage( + id = "id6", + cardsListContent( + titleText = frenchTranslations().titleText, + descriptionText = frenchTranslations().descriptionText, + primaryActionText = frenchTranslations().primaryActionText, + ), + surfaces = listOf(NEW_TAB_PAGE), + ), ), ), )