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),
+ ),
),
),
)