From a1a3eb70c7b2ab9a968df562dc9f49452d322efa Mon Sep 17 00:00:00 2001 From: Ana Capatina Date: Thu, 6 Nov 2025 18:01:44 +0000 Subject: [PATCH] Add What's New message type for RMF. Introduce a new message type for RMF called What's New. This message type uses a cards_list content structure, allowing for a title, description, and a list of items, each with its own title, description, image, and action. --- .../duckduckgo/common/ui/view/MessageCta.kt | 6 + .../src/main/res/drawable/ic_image_ai.xml | 110 ++++++++++++++++++ .../src/main/res/drawable/ic_key_import.xml | 80 +++++++++++++ .../src/main/res/drawable/ic_radar.xml | 85 ++++++++++++++ .../browser/newtab/NewTabLegacyPageView.kt | 6 + .../newtab/NewTabLegacyPageViewModel.kt | 1 + .../remotemessage/CommandActionMapper.kt | 3 +- .../remotemessage/RemoteMessageMapper.kt | 13 +++ .../api/MessageActionMapperPlugin.kt | 10 ++ .../remote/messaging/api/RemoteMessage.kt | 35 ++++++ .../impl/mappers/JsonActionMappers.kt | 14 +++ .../impl/mappers/JsonRemoteMessageMapper.kt | 42 ++++++- .../impl/mappers/RemoteMessageMapper.kt | 13 +++ .../impl/models/JsonRemoteMessagingConfig.kt | 3 + .../impl/newtab/RemoteMessageView.kt | 5 + .../impl/newtab/RemoteMessageViewModel.kt | 3 + .../messaging/fixtures/FakeActionPlugins.kt | 2 + 17 files changed, 426 insertions(+), 5 deletions(-) create mode 100644 android-design-system/design-system/src/main/res/drawable/ic_image_ai.xml create mode 100644 android-design-system/design-system/src/main/res/drawable/ic_key_import.xml create mode 100644 android-design-system/design-system/src/main/res/drawable/ic_radar.xml 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..a7361b11cd3b 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 @@ -28,6 +28,7 @@ import androidx.core.view.isVisible import com.airbnb.lottie.LottieAnimationView 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.view.MessageCta.MessageType.REMOTE_WHATS_NEW_MESSAGE import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.mobile.android.databinding.ViewMessageCtaBinding @@ -73,6 +74,9 @@ class MessageCta : FrameLayout { when (message.messageType) { REMOTE_MESSAGE -> setRemoteMessage(message) REMOTE_PROMO_MESSAGE -> setPromoMessage(message) + REMOTE_WHATS_NEW_MESSAGE -> { + // TODO: ANA set what's new message + } } } @@ -199,5 +203,7 @@ class MessageCta : FrameLayout { enum class MessageType { REMOTE_MESSAGE, REMOTE_PROMO_MESSAGE, + + REMOTE_WHATS_NEW_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..4626b1c6bb93 --- /dev/null +++ b/android-design-system/design-system/src/main/res/drawable/ic_image_ai.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..52b84fc1b38d --- /dev/null +++ b/android-design-system/design-system/src/main/res/drawable/ic_key_import.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..f6d9557703b0 --- /dev/null +++ b/android-design-system/design-system/src/main/res/drawable/ic_radar.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 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..8c5a2fd13728 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,10 @@ class NewTabLegacyPageView @JvmOverloads constructor( context.startActivity(browserNav.openInCurrentTab(context, url)) } + private fun submitUrlInContext(url: String) { + // TODO: ANA open a webview activity here + } + 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..8cc972c5cdd2 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_WHATS_NEW_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..3254c879a7a6 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") @@ -37,3 +38,12 @@ sealed class JsonActionType(val jsonValue: String) { data object NAVIGATION : JsonActionType("navigation") data object SURVEY : JsonActionType("survey") } + +data class JsonListItem( + val id: String, + val type: String, + val titleText: String, + val descriptionText: String, + val placeholder: String = "", + val primaryAction: JsonMessageAction?, +) 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 b8eceb590e71..cd9becb2d24a 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 @@ -67,12 +68,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) { @@ -85,6 +96,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 { @@ -97,6 +111,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) @@ -122,3 +137,23 @@ 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"), + ; + + companion object { + fun from(jsonValue: String): CardItemType { + return CardItemType.values().first { it.jsonValue == jsonValue } + } + } +} 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 93bf4dfb0571..aacdefd28707 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,19 +17,24 @@ 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 import com.duckduckgo.remote.messaging.api.Content.Small +import com.duckduckgo.remote.messaging.api.JsonListItem import com.duckduckgo.remote.messaging.api.JsonMessageAction import com.duckduckgo.remote.messaging.api.MessageActionMapperPlugin import com.duckduckgo.remote.messaging.api.RemoteMessage 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 @@ -85,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), @@ -92,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( @@ -112,7 +129,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() } @@ -140,14 +157,28 @@ private fun JsonMessageAction.toAction(actionMappers: Set?.toListItems(actionMappers: Set): List { + return this?.map { jsonItem -> + CardItem( + id = jsonItem.id.failIfEmpty(), + type = CardItemType.from(jsonItem.type), + 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( @@ -174,5 +205,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..4dbac828438c 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_WHATS_NEW_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 bdf9706179da..bf25a685114b 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 @@ -16,6 +16,7 @@ package com.duckduckgo.remote.messaging.impl.models +import com.duckduckgo.remote.messaging.api.JsonListItem import com.duckduckgo.remote.messaging.api.JsonMatchingAttribute import com.duckduckgo.remote.messaging.api.JsonMessageAction @@ -44,6 +45,7 @@ data class JsonContent( val secondaryAction: JsonMessageAction? = null, val actionText: String = "", val action: JsonMessageAction? = null, + val listItems: List? = null, ) data class JsonContentTranslations( @@ -72,4 +74,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..af011b399792 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,10 @@ class RemoteMessageView @JvmOverloads constructor( private fun submitUrl(url: String) { context.startActivity(browserNav.openInCurrentTab(context, url)) } + + private fun submitUrlInContext(url: String) { + // TODO: ANA open a webview activity here + } } @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(),