Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions PixelDefinitions/pixels/serp_settings.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// See https://app.asana.com/1/137249556945/project/1207908166761516/task/1211846254913290
{
"serp_settings_open_hide-ai-generated-images": {
// See DuckChatSettingsViewModel.kt for usage.
"description": "When the `Hide Ai Generated` option is opened and highlighted in SERP settings.",
"owners": ["mikescamell"],
"triggers": ["other"],
"suffixes": ["form_factor"],
"parameters": ["appVersion"]
},
"serp_settings_open_duck-ai": {
// See OpenNativeSettingsHandler.kt for usage.
"description": "When a user clicks the 'Open Duck.AI button' in the SERP settings menu which takes them to AI Features settings page.",
"owners": ["mikescamell"],
"triggers": ["other"],
"suffixes": ["form_factor"],
"parameters": ["appVersion"]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_EXPERIMENT
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_EXPERIMENTAL_OMNIBAR_SHOWN_COUNT
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_EXPERIMENTAL_OMNIBAR_SHOWN_DAILY
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_EXPERIMENTAL_OMNIBAR_TEXT_AREA_FOCUSED
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_HIDE_AI_GENERATED_IMAGES_BUTTON_CLICKED
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_IS_ENABLED_DAILY
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_KEYBOARD_RETURN_PRESSED
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_MENU_SETTING_OFF
Expand Down Expand Up @@ -92,6 +91,7 @@ import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_START_NEW_
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_START_NEW_CONVERSATION_BUTTON_CLICKED
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_USER_DISABLED
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_USER_ENABLED
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.SERP_SETTINGS_OPEN_HIDE_AI_GENERATED_IMAGES
import com.duckduckgo.duckchat.impl.repository.DuckChatFeatureRepository
import com.squareup.anvil.annotations.ContributesBinding
import com.squareup.anvil.annotations.ContributesMultibinding
Expand Down Expand Up @@ -154,7 +154,7 @@ enum class DuckChatPixelName(override val pixelName: String) : Pixel.PixelName {
DUCK_CHAT_ADDRESS_BAR_IS_ENABLED_DAILY("aichat_address_bar_is_enabled_daily"),
DUCK_CHAT_EXPERIMENTAL_ADDRESS_BAR_IS_ENABLED_DAILY("aichat_experimental_address_bar_is_enabled_daily"),
DUCK_CHAT_SEARCH_ASSIST_SETTINGS_BUTTON_CLICKED("aichat_search_assist_settings_button_clicked"),
DUCK_CHAT_HIDE_AI_GENERATED_IMAGES_BUTTON_CLICKED("aichat_hide_ai_generated_images_button_clicked"),
SERP_SETTINGS_OPEN_HIDE_AI_GENERATED_IMAGES("serp_settings_open_hide-ai-generated-images"),
DUCK_CHAT_START_NEW_CONVERSATION("aichat_start_new_conversation"),
DUCK_CHAT_START_NEW_CONVERSATION_BUTTON_CLICKED("aichat_start_new_conversation_button_clicked"),
DUCK_CHAT_OPEN_HISTORY("aichat_open_history"),
Expand Down Expand Up @@ -233,7 +233,7 @@ class DuckChatParamRemovalPlugin @Inject constructor() : PixelParamRemovalPlugin
DUCK_CHAT_ADDRESS_BAR_IS_ENABLED_DAILY.pixelName to PixelParameter.removeAtb(),
DUCK_CHAT_EXPERIMENTAL_ADDRESS_BAR_IS_ENABLED_DAILY.pixelName to PixelParameter.removeAtb(),
DUCK_CHAT_SEARCH_ASSIST_SETTINGS_BUTTON_CLICKED.pixelName to PixelParameter.removeAtb(),
DUCK_CHAT_HIDE_AI_GENERATED_IMAGES_BUTTON_CLICKED.pixelName to PixelParameter.removeAtb(),
SERP_SETTINGS_OPEN_HIDE_AI_GENERATED_IMAGES.pixelName to PixelParameter.removeAtb(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to move this to the settings module even though it's only fired here?

If we do then we'll need to make an API proposal to expose it.

I'm not sure it's worth it, we only renamed it to match other platforms.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think is fine leaving it here

DUCK_CHAT_START_NEW_CONVERSATION.pixelName to PixelParameter.removeAtb(),
DUCK_CHAT_START_NEW_CONVERSATION_BUTTON_CLICKED.pixelName to PixelParameter.removeAtb(),
DUCK_CHAT_OPEN_HISTORY.pixelName to PixelParameter.removeAtb(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ class DuckChatSettingsViewModel @AssistedInject constructor(
titleRes = R.string.duckAiSerpSettingsTitle,
),
)
pixel.fire(DuckChatPixelName.DUCK_CHAT_HIDE_AI_GENERATED_IMAGES_BUTTON_CLICKED)
pixel.fire(DuckChatPixelName.SERP_SETTINGS_OPEN_HIDE_AI_GENERATED_IMAGES)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,4 +550,11 @@ class DuckChatSettingsViewModelTest {
assertFalse(state.isFullScreenModeEnabled)
}
}

@Test
fun `when onDuckAiHideAiGeneratedImagesClicked then pixel is fired`() =
runTest {
testee.onDuckAiHideAiGeneratedImagesClicked()
verify(mockPixel).fire(DuckChatPixelName.SERP_SETTINGS_OPEN_HIDE_AI_GENERATED_IMAGES)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.duckduckgo.settings.impl.serpsettings.messaging
import android.content.Context
import android.content.Intent
import com.duckduckgo.app.di.AppCoroutineScope
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.common.utils.AppUrl
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.contentscopescripts.api.ContentScopeJsMessageHandlersPlugin
Expand All @@ -30,6 +31,7 @@ import com.duckduckgo.js.messaging.api.JsMessageHandler
import com.duckduckgo.js.messaging.api.JsMessaging
import com.duckduckgo.navigation.api.GlobalActivityStarter
import com.duckduckgo.settings.api.SettingsPageFeature
import com.duckduckgo.settings.impl.serpsettings.pixel.SerpSettingsPixelName.SERP_SETTINGS_OPEN_DUCK_AI
import com.squareup.anvil.annotations.ContributesMultibinding
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
Expand All @@ -47,6 +49,7 @@ class OpenNativeSettingsHandler @Inject constructor(
private val context: Context,
private val globalActivityStarter: GlobalActivityStarter,
private val settingsPageFeature: SettingsPageFeature,
private val pixel: Pixel,
) : ContentScopeJsMessageHandlersPlugin {

override fun getJsMessageHandler(): JsMessageHandler =
Expand All @@ -63,6 +66,7 @@ class OpenNativeSettingsHandler @Inject constructor(

when (val screenParam = params.optString("screen", "")) {
AI_FEATURES_SCREEN_NAME -> {
pixel.fire(SERP_SETTINGS_OPEN_DUCK_AI)
val intent = globalActivityStarter.startIntent(context, DuckChatNativeSettingsNoParams)
intent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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.settings.impl.serpsettings.pixel

import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.common.utils.plugins.pixel.PixelParamRemovalPlugin
import com.duckduckgo.common.utils.plugins.pixel.PixelParamRemovalPlugin.PixelParameter
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.settings.impl.serpsettings.pixel.SerpSettingsPixelName.SERP_SETTINGS_OPEN_DUCK_AI
import com.squareup.anvil.annotations.ContributesMultibinding
import javax.inject.Inject

enum class SerpSettingsPixelName(override val pixelName: String) : Pixel.PixelName {
SERP_SETTINGS_OPEN_DUCK_AI("serp_settings_open_duck-ai"),
}

@ContributesMultibinding(AppScope::class)
class SerpSettingsPixelParamRemovalPlugin @Inject constructor() : PixelParamRemovalPlugin {
override fun names(): List<Pair<String, Set<PixelParameter>>> {
return listOf(
SERP_SETTINGS_OPEN_DUCK_AI.pixelName to PixelParameter.removeAtb(),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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.settings.impl.serpsettings.fakes

import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.duckduckgo.navigation.api.GlobalActivityStarter
import com.duckduckgo.navigation.api.GlobalActivityStarter.ActivityParams

class FakeGlobalActivityStarter : GlobalActivityStarter {
val startedActivities = mutableListOf<ActivityParams>()
val startedDeeplinkActivities = mutableListOf<GlobalActivityStarter.DeeplinkActivityParams>()
var intentToReturn: Intent? = null

override fun start(
context: Context,
params: ActivityParams,
options: Bundle?,
) {
startedActivities.add(params)
}

override fun start(
context: Context,
deeplinkActivityParams: GlobalActivityStarter.DeeplinkActivityParams,
options: Bundle?,
) {
startedDeeplinkActivities.add(deeplinkActivityParams)
}

override fun startIntent(context: Context, params: ActivityParams): Intent? {
startedActivities.add(params)
return intentToReturn
}

override fun startIntent(
context: Context,
deeplinkActivityParams: GlobalActivityStarter.DeeplinkActivityParams,
): Intent? {
startedDeeplinkActivities.add(deeplinkActivityParams)
return intentToReturn
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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.settings.impl.serpsettings.fakes

import com.duckduckgo.app.statistics.pixels.Pixel

class FakePixel : Pixel {
val firedPixels = mutableListOf<String>()

override fun fire(
pixel: Pixel.PixelName,
parameters: Map<String, String>,
encodedParameters: Map<String, String>,
type: Pixel.PixelType,
) {
firedPixels.add(pixel.pixelName)
}

override fun fire(
pixelName: String,
parameters: Map<String, String>,
encodedParameters: Map<String, String>,
type: Pixel.PixelType,
) {
firedPixels.add(pixelName)
}

override fun enqueueFire(
pixel: Pixel.PixelName,
parameters: Map<String, String>,
encodedParameters: Map<String, String>,
) {
firedPixels.add(pixel.pixelName)
}

override fun enqueueFire(
pixelName: String,
parameters: Map<String, String>,
encodedParameters: Map<String, String>,
) {
firedPixels.add(pixelName)
}
}
Loading
Loading