Skip to content

Commit 7e45f9b

Browse files
authored
Rebranding: AI Features (#6481)
Task/Issue URL: https://app.asana.com/1/137249556945/project/488551667048375/task/1210882063286294?focus=true ### Description Update Duck.ai settings to AI Features ### Steps to test this PR _App Settings_ - [ ] Install from branch - [ ] Go to app settings - [ ] Check AI Features option is there with the updated icon _AI Features_ - [ ] Tap on AI Features - [ ] Check screen is updated to [latest specs](https://www.figma.com/design/tY8AG9QC2k7TATfoqbMM2s/%F0%9F%96%8D%EF%B8%8F-Post-launch-adjustments?node-id=167-60267&m=dev) _FF disabled (Optional)_ - [ ] Go to Settings > Feature Flag Inventory - [ ] Disable `subscriptionAIFeaturesRebranding` FF - [ ] Check App Settings shows Duck.ai and previous icon - [ ] Tap on Duck.ai and check changes are not applied ### UI changes | Before | After | | ------ | ----- | <img width="363" height="807" alt="Screenshot 2025-07-29 at 14 19 22" src="https://github.com/user-attachments/assets/50f0f915-ef5d-49c4-b4a0-108578040ead" />|<img width="371" height="827" alt="Screenshot 2025-07-29 at 14 19 27" src="https://github.com/user-attachments/assets/8b5a1992-f275-4ca1-9df0-c0c4d36b1b27" />| <img width="359" height="780" alt="Screenshot 2025-07-29 at 14 18 58" src="https://github.com/user-attachments/assets/83f37bf1-dbde-4393-b85d-a25fc70832fe" />|<img width="373" height="823" alt="Screenshot 2025-07-29 at 14 19 03" src="https://github.com/user-attachments/assets/0d184db8-7358-4895-874c-69f1805c551d" />|
1 parent 9ebf34c commit 7e45f9b

File tree

14 files changed

+160
-19
lines changed

14 files changed

+160
-19
lines changed

app/src/main/java/com/duckduckgo/app/settings/SettingsActivity.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ class SettingsActivity : DuckDuckGoActivity() {
298298
updatePrivacyPro(it.isPrivacyProEnabled)
299299
updateDuckPlayer(it.isDuckPlayerEnabled)
300300
updateThreatProtection(it.isNewThreatProtectionSettingsEnabled)
301-
updateDuckChat(it.isDuckChatEnabled)
301+
updateDuckChat(it.isDuckChatEnabled, it.isAiFeaturesRebrandingEnabled)
302302
updateVoiceSearchVisibility(it.isVoiceSearchVisible)
303303
updateAddWidgetInProtections(it.isAddWidgetInProtectionsVisible, it.widgetsInstalled)
304304
}
@@ -335,9 +335,20 @@ class SettingsActivity : DuckDuckGoActivity() {
335335
}
336336
}
337337

338-
private fun updateDuckChat(isDuckChatEnabled: Boolean) {
338+
private fun updateDuckChat(isDuckChatEnabled: Boolean, isAiFeaturesRebrandingEnabled: Boolean) {
339339
if (isDuckChatEnabled) {
340+
val imageRes: Int
341+
val titleRes: Int
342+
if (isAiFeaturesRebrandingEnabled) {
343+
imageRes = com.duckduckgo.mobile.android.R.drawable.ai_general_color_24
344+
titleRes = R.string.settingsDuckAiRebranding
345+
} else {
346+
imageRes = com.duckduckgo.mobile.android.R.drawable.ic_ai_chat_color_24
347+
titleRes = R.string.settingsDuckAi
348+
}
340349
viewsMain.includeDuckChatSetting.duckChatSetting.show()
350+
viewsMain.includeDuckChatSetting.leadingIcon.setImageResource(imageRes)
351+
viewsMain.includeDuckChatSetting.primaryText.setText(titleRes)
341352
} else {
342353
viewsMain.includeDuckChatSetting.duckChatSetting.gone()
343354
}

app/src/main/java/com/duckduckgo/app/settings/SettingsViewModel.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ import com.duckduckgo.mobile.android.app.tracking.AppTrackingProtection
8282
import com.duckduckgo.settings.api.SettingsPageFeature
8383
import com.duckduckgo.subscriptions.api.PrivacyProUnifiedFeedback
8484
import com.duckduckgo.subscriptions.api.PrivacyProUnifiedFeedback.PrivacyProFeedbackSource.DDG_SETTINGS
85+
import com.duckduckgo.subscriptions.api.SubscriptionRebrandingFeatureToggle
8586
import com.duckduckgo.subscriptions.api.Subscriptions
8687
import com.duckduckgo.sync.api.DeviceSyncState
8788
import com.duckduckgo.voice.api.VoiceSearchAvailability
@@ -124,6 +125,7 @@ class SettingsViewModel @Inject constructor(
124125
private val settingsPageFeature: SettingsPageFeature,
125126
private val widgetCapabilities: WidgetCapabilities,
126127
private val postCtaExperienceExperiment: PostCtaExperienceExperiment,
128+
private val rebrandingFeatureToggle: SubscriptionRebrandingFeatureToggle,
127129
) : ViewModel(), DefaultLifecycleObserver {
128130

129131
data class ViewState(
@@ -141,6 +143,7 @@ class SettingsViewModel @Inject constructor(
141143
val isVoiceSearchVisible: Boolean = false,
142144
val isAddWidgetInProtectionsVisible: Boolean = false,
143145
val widgetsInstalled: Boolean = false,
146+
val isAiFeaturesRebrandingEnabled: Boolean = false,
144147
)
145148

146149
sealed class Command {
@@ -218,6 +221,7 @@ class SettingsViewModel @Inject constructor(
218221
widgetsInstalled = withContext(dispatcherProvider.io()) {
219222
widgetCapabilities.hasInstalledWidgets
220223
},
224+
isAiFeaturesRebrandingEnabled = rebrandingFeatureToggle.isAIFeaturesRebrandingEnabled(),
221225
),
222226
)
223227
}

app/src/main/res/values/donottranslate.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,7 @@
6969
<!-- Privacy Pro Rebranding-->
7070
<string name="aboutDescriptionBrandUpdate2025Rebranding">Welcome to the Duck Side!\n\nDuckDuckGo is the independent online protection company for anyone who wants to take back control of their personal information.\n\nWe believe the best way to protect your personal information from hackers, scammers, and privacy-invasive companies is to stop it from being collected at all. That\'s why millions of people <annotation type="chart_comparison">choose DuckDuckGo over Chrome and other browsers</annotation> to search and browse online. Our built-in search engine is like Google but never tracks your searches. And our browsing protections, such as ad tracker blocking and cookie blocking, help stop other companies from collecting your data. Oh, and our browser is free — we make money from <annotation type="privacy_respecting_ads">privacy-respecting search ads</annotation>, not by exploiting your data.\n\nIn addition, we also offer <annotation type="ppro_help_page">DuckDuckGo subscription</annotation> integrated into our browser. Subscribers get even more privacy protection with a fast and simple <annotation type="vpn_help_page">VPN</annotation> that helps secure your online activity, access to advanced AI models in our private AI chat service Duck.ai, a service that finds and removes your personal information from people-finder sites, and a service that restores your identity should it be stolen.\n\nTake back control of your personal information with the browser designed for data protection, not data collection.</string>
7171
<string name="onboardingPrivacyProDaxDialogDescriptionRebranding"><![CDATA[DuckDuckGo also has an <b>optional paid subscription</b>, with a secure <b>VPN</b> and advanced, private AI.]]></string>
72+
<string name="settingsDuckAiRebranding">AI Features</string>
73+
<string name="settingsDuckAi">Duck.ai</string>
7274

7375
</resources>

app/src/test/java/com/duckduckgo/app/settings/SettingsViewModelTest.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import com.duckduckgo.feature.toggles.api.Toggle.State
4141
import com.duckduckgo.mobile.android.app.tracking.AppTrackingProtection
4242
import com.duckduckgo.settings.api.SettingsPageFeature
4343
import com.duckduckgo.subscriptions.api.PrivacyProUnifiedFeedback
44+
import com.duckduckgo.subscriptions.api.SubscriptionRebrandingFeatureToggle
4445
import com.duckduckgo.subscriptions.api.Subscriptions
4546
import com.duckduckgo.sync.api.DeviceSyncState
4647
import com.duckduckgo.voice.api.VoiceSearchAvailability
@@ -102,6 +103,8 @@ class SettingsViewModelTest {
102103
private val mockDuckAiFeatureState: DuckAiFeatureState = mock()
103104
private val duckAiShowSettingsFlow = MutableStateFlow(false)
104105

106+
private val mockRebrandingFeatureToggle: SubscriptionRebrandingFeatureToggle = mock()
107+
105108
@Before
106109
fun before() = runTest {
107110
whenever(dispatcherProviderMock.io()).thenReturn(coroutineTestRule.testDispatcher)
@@ -131,6 +134,7 @@ class SettingsViewModelTest {
131134
settingsPageFeature = fakeSettingsPageFeature,
132135
widgetCapabilities = mockWidgetCapabilities,
133136
postCtaExperienceExperiment = mockPostCtaExperienceExperiment,
137+
rebrandingFeatureToggle = mockRebrandingFeatureToggle,
134138
)
135139
}
136140

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<!--
2+
~ Copyright (c) 2025 DuckDuckGo
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
17+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
18+
xmlns:aapt="http://schemas.android.com/aapt"
19+
android:width="24dp"
20+
android:height="24dp"
21+
android:viewportWidth="24"
22+
android:viewportHeight="24">
23+
<path
24+
android:pathData="M10.63,5.081a0.75,0.75 0,0 1,0.745 -0.581,0.75 0.75,0 0,1 0.744,0.581l0.43,1.718A6.394,6.394 0,0 0,17.2 11.45l1.718,0.43a0.75,0.75 0,0 1,0.581 0.75,0.75 0.75,0 0,1 -0.581,0.738l-1.718,0.43a6.394,6.394 0,0 0,-4.652 4.652l-0.43,1.718a0.75,0.75 0,0 1,-0.744 0.581,0.75 0.75,0 0,1 -0.744,-0.581l-0.43,-1.718a6.394,6.394 0,0 0,-4.65 -4.65l-1.718,-0.43a0.75,0.75 0,0 1,-0.581 -0.737,0.75 0.75,0 0,1 0.581,-0.751l1.718,-0.43A6.394,6.394 0,0 0,10.2 6.8l0.43,-1.718Z">
25+
<aapt:attr name="android:fillColor">
26+
<gradient
27+
android:startX="11.375"
28+
android:startY="20.75"
29+
android:endX="11.375"
30+
android:endY="4.5"
31+
android:type="linear">
32+
<item android:offset="0" android:color="#FF5981F3"/>
33+
<item android:offset="1" android:color="#FFA7B7FD"/>
34+
</gradient>
35+
</aapt:attr>
36+
</path>
37+
<path
38+
android:pathData="M11.53,3.256a1.995,1.995 0,0 1,1.747 1.338l0.055,0.184 0.43,1.717a5.145,5.145 0,0 0,3.743 3.743l1.717,0.43c1.022,0.256 1.535,1.148 1.528,1.974 -0.007,0.816 -0.521,1.688 -1.528,1.94l-1.717,0.43a5.145,5.145 0,0 0,-3.743 3.743l-0.43,1.717C13.078,21.486 12.196,22 11.375,22c-0.821,0 -1.703,-0.514 -1.957,-1.528l-0.43,-1.717a5.145,5.145 0,0 0,-3.743 -3.743l-1.717,-0.43C2.521,14.33 2.007,13.458 2,12.642c-0.007,-0.826 0.506,-1.718 1.528,-1.974l1.717,-0.43a5.145,5.145 0,0 0,3.743 -3.743l0.43,-1.717 0.055,-0.184c0.309,-0.893 1.132,-1.344 1.902,-1.344l0.154,0.006ZM11.374,4.5a0.75,0.75 0,0 0,-0.744 0.581L10.2,6.8l-0.057,0.213a6.394,6.394 0,0 1,-4.595 4.44l-1.718,0.429a0.75,0.75 0,0 0,-0.58 0.75,0.75 0.75,0 0,0 0.58,0.738l1.718,0.43a6.394,6.394 0,0 1,4.652 4.65l0.43,1.718c0.085,0.339 0.343,0.53 0.623,0.572l0.121,0.009a0.75,0.75 0,0 0,0.698 -0.445l0.046,-0.136 0.43,-1.718a6.394,6.394 0,0 1,4.44 -4.595l0.212,-0.057 1.718,-0.43a0.745,0.745 0,0 0,0.571 -0.617l0.01,-0.12a0.75,0.75 0,0 0,-0.444 -0.704l-0.137,-0.047 -1.718,-0.43a6.394,6.394 0,0 1,-4.595 -4.44l-0.055,-0.21 -0.43,-1.718a0.75,0.75 0,0 0,-0.744 -0.581Z">
39+
<aapt:attr name="android:fillColor">
40+
<gradient
41+
android:startX="11.375"
42+
android:startY="3.25"
43+
android:endX="11.375"
44+
android:endY="22"
45+
android:type="linear">
46+
<item android:offset="0" android:color="#FF557FF3"/>
47+
<item android:offset="1" android:color="#FF2B55CA"/>
48+
</gradient>
49+
</aapt:attr>
50+
</path>
51+
<path
52+
android:pathData="M18.62,2.2c0.066,-0.266 0.444,-0.266 0.51,0l0.305,1.217a1.579,1.579 0,0 0,1.148 1.148l1.218,0.305c0.265,0.066 0.265,0.444 0,0.51l-1.218,0.305a1.579,1.579 0,0 0,-1.148 1.148l-0.305,1.218c-0.066,0.265 -0.444,0.265 -0.51,0l-0.305,-1.218a1.579,1.579 0,0 0,-1.148 -1.148l-1.218,-0.305c-0.265,-0.066 -0.265,-0.444 0,-0.51l1.218,-0.305a1.579,1.579 0,0 0,1.148 -1.148l0.305,-1.218Z"
53+
android:fillColor="#3969EF"/>
54+
</vector>

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/DuckChatSettingsActivity.kt

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import com.duckduckgo.browser.api.ui.BrowserScreens.WebViewActivityWithParams
3131
import com.duckduckgo.common.ui.DuckDuckGoActivity
3232
import com.duckduckgo.common.ui.spans.DuckDuckGoClickableSpan
3333
import com.duckduckgo.common.ui.view.addClickableSpan
34+
import com.duckduckgo.common.ui.view.gone
35+
import com.duckduckgo.common.ui.view.show
3436
import com.duckduckgo.common.ui.viewbinding.viewBinding
3537
import com.duckduckgo.di.scopes.ActivityScope
3638
import com.duckduckgo.duckchat.api.DuckChatSettingsNoParams
@@ -115,7 +117,25 @@ class DuckChatSettingsActivity : DuckDuckGoActivity() {
115117
}
116118

117119
private fun renderViewState(viewState: ViewState) {
118-
binding.userEnabledDuckChatToggle.quietlySetIsChecked(viewState.isDuckChatUserEnabled, userEnabledDuckChatToggleListener)
120+
if (viewState.isRebrandingAiFeaturesEnabled) {
121+
binding.userEnabledDuckChatToggleRebranding.quietlySetIsChecked(viewState.isDuckChatUserEnabled, userEnabledDuckChatToggleListener)
122+
binding.duckChatSettingsTitle.setText(R.string.duck_chat_title_rebranding)
123+
binding.duckChatToggleSettingsTitle.setText(R.string.duck_chat_settings_activity_description_rebranding)
124+
binding.userEnabledDuckChatToggle.gone()
125+
binding.userEnabledDuckChatToggleRebranding.show()
126+
binding.duckChatToggleSettingsTitle.setText(R.string.duck_chat_show_in_heading_rebranding)
127+
binding.showDuckChatSearchSettingsLink.setPrimaryText(getString(R.string.duck_chat_assist_settings_title_rebranding))
128+
binding.showDuckChatSearchSettingsLink.setSecondaryText(getString(R.string.duck_chat_assist_settings_description_rebranding))
129+
} else {
130+
binding.userEnabledDuckChatToggle.quietlySetIsChecked(viewState.isDuckChatUserEnabled, userEnabledDuckChatToggleListener)
131+
binding.duckChatSettingsTitle.setText(R.string.duck_chat_title)
132+
binding.duckChatSettingsText.setText(R.string.duck_chat_settings_activity_description)
133+
binding.userEnabledDuckChatToggle.show()
134+
binding.userEnabledDuckChatToggleRebranding.gone()
135+
binding.duckChatToggleSettingsTitle.setText(R.string.duck_chat_show_in_heading)
136+
binding.showDuckChatSearchSettingsLink.setPrimaryText(getString(R.string.duck_chat_assist_settings_title))
137+
binding.showDuckChatSearchSettingsLink.setSecondaryText(getString(R.string.duck_chat_assist_settings_description))
138+
}
119139

120140
binding.duckAiInputScreenEnabledToggle.apply {
121141
isVisible = viewState.shouldShowInputScreenToggle

duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/DuckChatSettingsViewModel.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.duckduckgo.duckchat.impl.DuckChatInternal
2626
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName
2727
import com.duckduckgo.duckchat.impl.ui.DuckChatSettingsViewModel.Command.OpenLink
2828
import com.duckduckgo.duckchat.impl.ui.DuckChatSettingsViewModel.Command.OpenLinkInNewTab
29+
import com.duckduckgo.subscriptions.api.SubscriptionRebrandingFeatureToggle
2930
import javax.inject.Inject
3031
import kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST
3132
import kotlinx.coroutines.channels.Channel
@@ -40,6 +41,7 @@ class DuckChatSettingsViewModel @Inject constructor(
4041
private val duckChat: DuckChatInternal,
4142
private val pixel: Pixel,
4243
private val experimentalThemingDataStore: ExperimentalThemingDataStore,
44+
private val rebrandingAiFeaturesEnabled: SubscriptionRebrandingFeatureToggle,
4345
) : ViewModel() {
4446

4547
private val commandChannel = Channel<Command>(capacity = 1, onBufferOverflow = DROP_OLDEST)
@@ -53,6 +55,7 @@ class DuckChatSettingsViewModel @Inject constructor(
5355
val shouldShowInputScreenToggle: Boolean = false,
5456
val shouldShowBrowserMenuToggle: Boolean = false,
5557
val shouldShowAddressBarToggle: Boolean = false,
58+
val isRebrandingAiFeaturesEnabled: Boolean = false,
5659
)
5760

5861
val viewState = combine(
@@ -69,6 +72,7 @@ class DuckChatSettingsViewModel @Inject constructor(
6972
shouldShowInputScreenToggle = isDuckChatUserEnabled && duckChat.isInputScreenFeatureAvailable(),
7073
shouldShowBrowserMenuToggle = isDuckChatUserEnabled,
7174
shouldShowAddressBarToggle = isDuckChatUserEnabled && duckChat.isAddressBarEntryPointEnabled(),
75+
isRebrandingAiFeaturesEnabled = rebrandingAiFeaturesEnabled.isAIFeaturesRebrandingEnabled(),
7276
)
7377
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), ViewState())
7478

duckchat/duckchat-impl/src/main/res/layout/activity_duck_chat_settings.xml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@
7878
app:primaryText="@string/duck_chat_enable_duck_ai_setting"
7979
app:showSwitch="true"/>
8080

81+
<com.duckduckgo.common.ui.view.listitem.TwoLineListItem
82+
android:id="@+id/userEnabledDuckChatToggleRebranding"
83+
android:layout_width="match_parent"
84+
android:layout_height="wrap_content"
85+
android:visibility="gone"
86+
app:leadingIcon="@drawable/ic_ai_chat_24"
87+
app:leadingIconBackground="circular"
88+
app:primaryText="@string/duck_chat_enable_duck_ai_setting_rebranding"
89+
app:secondaryText="@string/duck_chat_enable_duck_ai_setting_description_rebranding"
90+
app:showSwitch="true" />
91+
8192
<com.duckduckgo.common.ui.view.listitem.TwoLineListItem
8293
android:id="@+id/duckAiInputScreenEnabledToggle"
8394
android:layout_width="match_parent"
@@ -121,7 +132,6 @@
121132
app:leadingIconBackground="circular"
122133
app:showSwitch="false"/>
123134

124-
125135
</LinearLayout>
126136
</ScrollView>
127137
</androidx.coordinatorlayout.widget.CoordinatorLayout>

duckchat/duckchat-impl/src/main/res/values/donottranslate.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,13 @@
4040
<string name="duck_ai_paid_settings_page_description"><![CDATA[Chat privately with subscriber-exclusive AI models.\n<a href="">Learn More</a>]]></string>
4141
<string name="duck_ai_paid_settings_open_duck_ai">Open Duck.ai</string>
4242
<string name="duck_ai_paid_settings_learn_more_title">Learn more</string>
43+
44+
<!-- Rebranding-->
45+
<string name="duck_chat_title_rebranding">AI Features</string>
46+
<string name="duck_chat_settings_activity_description_rebranding">DuckDuckGo AI features are private and optional.\nYour data is not used to train AI.\n<annotation type="learn_more_link">Learn More</annotation></string>
47+
<string name="duck_chat_enable_duck_ai_setting_rebranding">Duck.ai</string>
48+
<string name="duck_chat_enable_duck_ai_setting_description_rebranding">Chat anonymously with popular 3rd-party AI chat models</string>
49+
<string name="duck_chat_show_in_heading_rebranding">Duck.ai Shortcuts</string>
50+
<string name="duck_chat_assist_settings_title_rebranding">Search Assist Settings</string>
51+
<string name="duck_chat_assist_settings_description_rebranding">Choose how often you want AI-Assisted answers to appear in your searches</string>
4352
</resources>

0 commit comments

Comments
 (0)