Skip to content

Commit 3e64e60

Browse files
committed
move Duck.ai shortcut settings to a separate screen
1 parent 8a72e93 commit 3e64e60

34 files changed

+376
-188
lines changed

duckchat/duckchat-impl/src/main/AndroidManifest.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,15 @@
1818

1919
<application>
2020
<activity
21-
android:name="com.duckduckgo.duckchat.impl.ui.DuckChatSettingsActivity"
21+
android:name="com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsActivity"
2222
android:exported="false"
2323
android:label="@string/duck_chat_title"
2424
android:parentActivityName="com.duckduckgo.app.settings.SettingsActivity" />
25+
<activity
26+
android:name="com.duckduckgo.duckchat.impl.ui.settings.DuckAiShortcutSettingsActivity"
27+
android:exported="false"
28+
android:label="@string/duck_ai_shortcut_settings_title"
29+
android:parentActivityName="com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsActivity" />
2530
<activity
2631
android:name="com.duckduckgo.duckchat.impl.ui.DuckChatWebViewActivity"
2732
android:exported="false"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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+
package com.duckduckgo.duckchat.impl.ui.settings
18+
19+
import android.os.Bundle
20+
import android.widget.CompoundButton
21+
import androidx.core.view.isVisible
22+
import androidx.lifecycle.Lifecycle
23+
import androidx.lifecycle.flowWithLifecycle
24+
import androidx.lifecycle.lifecycleScope
25+
import com.duckduckgo.anvil.annotations.InjectWith
26+
import com.duckduckgo.common.ui.DuckDuckGoActivity
27+
import com.duckduckgo.common.ui.viewbinding.viewBinding
28+
import com.duckduckgo.di.scopes.ActivityScope
29+
import com.duckduckgo.duckchat.impl.databinding.ActivityDuckAiShortcutSettingsBinding
30+
import com.duckduckgo.duckchat.impl.ui.settings.DuckAiShortcutSettingsViewModel.ViewState
31+
import kotlinx.coroutines.flow.launchIn
32+
import kotlinx.coroutines.flow.onEach
33+
34+
@InjectWith(ActivityScope::class)
35+
class DuckAiShortcutSettingsActivity : DuckDuckGoActivity() {
36+
37+
private val viewModel: DuckAiShortcutSettingsViewModel by bindViewModel()
38+
private val binding: ActivityDuckAiShortcutSettingsBinding by viewBinding()
39+
40+
private val menuToggleListener =
41+
CompoundButton.OnCheckedChangeListener { _, isChecked ->
42+
viewModel.onShowDuckChatInMenuToggled(isChecked)
43+
}
44+
45+
private val addressBarToggleListener =
46+
CompoundButton.OnCheckedChangeListener { _, isChecked ->
47+
viewModel.onShowDuckChatInAddressBarToggled(isChecked)
48+
}
49+
50+
override fun onCreate(savedInstanceState: Bundle?) {
51+
super.onCreate(savedInstanceState)
52+
53+
setContentView(binding.root)
54+
55+
setupToolbar(binding.includeToolbar.toolbar)
56+
57+
observeViewModel()
58+
}
59+
60+
private fun observeViewModel() {
61+
viewModel.viewState
62+
.flowWithLifecycle(lifecycle, Lifecycle.State.RESUMED)
63+
.onEach { renderViewState(it) }
64+
.launchIn(lifecycleScope)
65+
}
66+
67+
private fun renderViewState(viewState: ViewState) {
68+
binding.showDuckAiInMenuToggle.apply {
69+
quietlySetIsChecked(viewState.showInBrowserMenu, menuToggleListener)
70+
}
71+
binding.showDuckAiInAddressBarToggle.apply {
72+
isVisible = viewState.shouldShowAddressBarToggle
73+
quietlySetIsChecked(viewState.showInAddressBar, addressBarToggleListener)
74+
}
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
package com.duckduckgo.duckchat.impl.ui.settings
18+
19+
import androidx.lifecycle.ViewModel
20+
import androidx.lifecycle.viewModelScope
21+
import com.duckduckgo.anvil.annotations.ContributesViewModel
22+
import com.duckduckgo.app.statistics.pixels.Pixel
23+
import com.duckduckgo.common.ui.experiments.visual.store.ExperimentalThemingDataStore
24+
import com.duckduckgo.di.scopes.ActivityScope
25+
import com.duckduckgo.duckchat.impl.DuckChatInternal
26+
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName
27+
import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.Command.OpenLink
28+
import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.Command.OpenLinkInNewTab
29+
import com.duckduckgo.subscriptions.api.SubscriptionRebrandingFeatureToggle
30+
import javax.inject.Inject
31+
import kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST
32+
import kotlinx.coroutines.channels.Channel
33+
import kotlinx.coroutines.flow.SharingStarted
34+
import kotlinx.coroutines.flow.combine
35+
import kotlinx.coroutines.flow.receiveAsFlow
36+
import kotlinx.coroutines.flow.stateIn
37+
import kotlinx.coroutines.launch
38+
39+
@ContributesViewModel(ActivityScope::class)
40+
class DuckAiShortcutSettingsViewModel @Inject constructor(
41+
private val duckChat: DuckChatInternal,
42+
) : ViewModel() {
43+
44+
data class ViewState(
45+
val showInBrowserMenu: Boolean = false,
46+
val showInAddressBar: Boolean = false,
47+
val shouldShowAddressBarToggle: Boolean = false,
48+
)
49+
50+
val viewState = combine(
51+
duckChat.observeShowInBrowserMenuUserSetting(),
52+
duckChat.observeShowInAddressBarUserSetting(),
53+
) { showInBrowserMenu, showInAddressBar ->
54+
ViewState(
55+
showInBrowserMenu = showInBrowserMenu,
56+
showInAddressBar = showInAddressBar,
57+
shouldShowAddressBarToggle = duckChat.isAddressBarEntryPointEnabled(),
58+
)
59+
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), ViewState())
60+
61+
fun onShowDuckChatInMenuToggled(checked: Boolean) {
62+
viewModelScope.launch {
63+
duckChat.setShowInBrowserMenuUserSetting(checked)
64+
}
65+
}
66+
67+
fun onShowDuckChatInAddressBarToggled(checked: Boolean) {
68+
viewModelScope.launch {
69+
duckChat.setShowInAddressBarUserSetting(checked)
70+
}
71+
}
72+
}

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

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.duckduckgo.duckchat.impl.ui
17+
package com.duckduckgo.duckchat.impl.ui.settings
1818

19+
import android.content.Intent
1920
import android.os.Bundle
2021
import android.view.View
2122
import android.widget.CompoundButton
@@ -28,6 +29,7 @@ import com.duckduckgo.anvil.annotations.ContributeToActivityStarter
2829
import com.duckduckgo.anvil.annotations.InjectWith
2930
import com.duckduckgo.app.statistics.pixels.Pixel
3031
import com.duckduckgo.app.tabs.BrowserNav
32+
import com.duckduckgo.browser.api.ui.BrowserScreens.FeedbackActivityWithEmptyParams
3133
import com.duckduckgo.browser.api.ui.BrowserScreens.WebViewActivityWithParams
3234
import com.duckduckgo.common.ui.DuckDuckGoActivity
3335
import com.duckduckgo.common.ui.spans.DuckDuckGoClickableSpan
@@ -40,11 +42,12 @@ import com.duckduckgo.duckchat.api.DuckChatSettingsNoParams
4042
import com.duckduckgo.duckchat.impl.R
4143
import com.duckduckgo.duckchat.impl.databinding.ActivityDuckChatSettingsBinding
4244
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_SETTINGS_DISPLAYED
43-
import com.duckduckgo.duckchat.impl.ui.DuckChatSettingsViewModel.ViewState
45+
import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.ViewState
4446
import com.duckduckgo.navigation.api.GlobalActivityStarter
4547
import javax.inject.Inject
4648
import kotlinx.coroutines.flow.launchIn
4749
import kotlinx.coroutines.flow.onEach
50+
import com.duckduckgo.mobile.android.R as CommonR
4851

4952
@InjectWith(ActivityScope::class)
5053
@ContributeToActivityStarter(DuckChatSettingsNoParams::class, screenName = "duckai.settings")
@@ -63,16 +66,6 @@ class DuckChatSettingsActivity : DuckDuckGoActivity() {
6366
viewModel.onDuckAiInputScreenToggled(isChecked)
6467
}
6568

66-
private val menuToggleListener =
67-
CompoundButton.OnCheckedChangeListener { _, isChecked ->
68-
viewModel.onShowDuckChatInMenuToggled(isChecked)
69-
}
70-
71-
private val addressBarToggleListener =
72-
CompoundButton.OnCheckedChangeListener { _, isChecked ->
73-
viewModel.onShowDuckChatInAddressBarToggled(isChecked)
74-
}
75-
7669
@Inject
7770
lateinit var globalActivityStarter: GlobalActivityStarter
7871

@@ -114,7 +107,6 @@ class DuckChatSettingsActivity : DuckDuckGoActivity() {
114107
binding.duckChatSettingsTitle.setText(R.string.duck_chat_title_rebranding)
115108
binding.userEnabledDuckChatToggle.gone()
116109
binding.userEnabledDuckChatToggleRebranding.show()
117-
binding.duckChatToggleSettingsTitle.setText(R.string.duck_chat_show_in_heading_rebranding)
118110
binding.showDuckChatSearchSettingsLink.setPrimaryText(getString(R.string.duck_chat_assist_settings_title_rebranding))
119111
binding.showDuckChatSearchSettingsLink.setSecondaryText(getString(R.string.duck_chat_assist_settings_description_rebranding))
120112
} else {
@@ -124,7 +116,6 @@ class DuckChatSettingsActivity : DuckDuckGoActivity() {
124116
binding.duckChatSettingsTitle.setText(R.string.duck_chat_title)
125117
binding.userEnabledDuckChatToggle.show()
126118
binding.userEnabledDuckChatToggleRebranding.gone()
127-
binding.duckChatToggleSettingsTitle.setText(R.string.duck_chat_show_in_heading)
128119
binding.showDuckChatSearchSettingsLink.setPrimaryText(getString(R.string.duck_chat_assist_settings_title))
129120
binding.showDuckChatSearchSettingsLink.setSecondaryText(getString(R.string.duck_chat_assist_settings_description))
130121
}
@@ -149,15 +140,9 @@ class DuckChatSettingsActivity : DuckDuckGoActivity() {
149140
quietlySetIsChecked(viewState.isInputScreenEnabled, inputScreenToggleListener)
150141
}
151142

152-
binding.duckChatToggleSettingsTitle.isVisible = viewState.isDuckChatUserEnabled
153-
154-
binding.showDuckChatInMenuToggle.apply {
155-
isVisible = viewState.shouldShowAddressBarToggle
156-
quietlySetIsChecked(viewState.showInBrowserMenu, menuToggleListener)
157-
}
158-
binding.showDuckChatInAddressBarToggle.apply {
159-
isVisible = viewState.shouldShowAddressBarToggle
160-
quietlySetIsChecked(viewState.showInAddressBar, addressBarToggleListener)
143+
binding.duckAiShortcuts.isVisible = viewState.shouldShowShortcuts
144+
binding.duckAiShortcuts.setOnClickListener {
145+
viewModel.onDuckAiShortcutsClicked()
161146
}
162147
binding.showDuckChatSearchSettingsLink.setOnClickListener {
163148
viewModel.duckChatSearchAISettingsClicked()
@@ -178,6 +163,11 @@ class DuckChatSettingsActivity : DuckDuckGoActivity() {
178163
is DuckChatSettingsViewModel.Command.OpenLinkInNewTab -> {
179164
startActivity(browserNav.openInNewTab(this@DuckChatSettingsActivity, command.link))
180165
}
166+
167+
is DuckChatSettingsViewModel.Command.OpenShortcutSettings -> {
168+
val intent = Intent(this, DuckAiShortcutSettingsActivity::class.java)
169+
startActivity(intent)
170+
}
181171
}
182172
}
183173
}

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

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,18 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.duckduckgo.duckchat.impl.ui
17+
package com.duckduckgo.duckchat.impl.ui.settings
1818

1919
import androidx.lifecycle.ViewModel
2020
import androidx.lifecycle.viewModelScope
2121
import com.duckduckgo.anvil.annotations.ContributesViewModel
2222
import com.duckduckgo.app.statistics.pixels.Pixel
23-
import com.duckduckgo.common.ui.experiments.visual.store.ExperimentalThemingDataStore
2423
import com.duckduckgo.di.scopes.ActivityScope
2524
import com.duckduckgo.duckchat.impl.DuckChatInternal
2625
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName
27-
import com.duckduckgo.duckchat.impl.ui.DuckChatSettingsViewModel.Command.OpenLink
28-
import com.duckduckgo.duckchat.impl.ui.DuckChatSettingsViewModel.Command.OpenLinkInNewTab
26+
import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.Command.OpenLink
27+
import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.Command.OpenLinkInNewTab
28+
import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.Command.OpenShortcutSettings
2929
import com.duckduckgo.subscriptions.api.SubscriptionRebrandingFeatureToggle
3030
import javax.inject.Inject
3131
import kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST
@@ -40,7 +40,6 @@ import kotlinx.coroutines.launch
4040
class DuckChatSettingsViewModel @Inject constructor(
4141
private val duckChat: DuckChatInternal,
4242
private val pixel: Pixel,
43-
private val experimentalThemingDataStore: ExperimentalThemingDataStore,
4443
private val rebrandingAiFeaturesEnabled: SubscriptionRebrandingFeatureToggle,
4544
) : ViewModel() {
4645

@@ -50,35 +49,28 @@ class DuckChatSettingsViewModel @Inject constructor(
5049
data class ViewState(
5150
val isDuckChatUserEnabled: Boolean = false,
5251
val isInputScreenEnabled: Boolean = false,
53-
val showInBrowserMenu: Boolean = false,
54-
val showInAddressBar: Boolean = false,
52+
val shouldShowShortcuts: Boolean = false,
5553
val shouldShowInputScreenToggle: Boolean = false,
56-
val shouldShowBrowserMenuToggle: Boolean = false,
57-
val shouldShowAddressBarToggle: Boolean = false,
5854
val isRebrandingAiFeaturesEnabled: Boolean = false,
5955
)
6056

6157
val viewState = combine(
6258
duckChat.observeEnableDuckChatUserSetting(),
6359
duckChat.observeInputScreenUserSettingEnabled(),
64-
duckChat.observeShowInBrowserMenuUserSetting(),
65-
duckChat.observeShowInAddressBarUserSetting(),
66-
) { isDuckChatUserEnabled, isInputScreenEnabled, showInBrowserMenu, showInAddressBar ->
60+
) { isDuckChatUserEnabled, isInputScreenEnabled ->
6761
ViewState(
6862
isDuckChatUserEnabled = isDuckChatUserEnabled,
6963
isInputScreenEnabled = isInputScreenEnabled,
70-
showInBrowserMenu = showInBrowserMenu,
71-
showInAddressBar = showInAddressBar,
64+
shouldShowShortcuts = isDuckChatUserEnabled,
7265
shouldShowInputScreenToggle = isDuckChatUserEnabled && duckChat.isInputScreenFeatureAvailable(),
73-
shouldShowBrowserMenuToggle = isDuckChatUserEnabled,
74-
shouldShowAddressBarToggle = isDuckChatUserEnabled && duckChat.isAddressBarEntryPointEnabled(),
7566
isRebrandingAiFeaturesEnabled = rebrandingAiFeaturesEnabled.isAIFeaturesRebrandingEnabled(),
7667
)
7768
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), ViewState())
7869

7970
sealed class Command {
8071
data class OpenLink(val link: String) : Command()
8172
data class OpenLinkInNewTab(val link: String) : Command()
73+
data object OpenShortcutSettings : Command()
8274
}
8375

8476
fun onDuckChatUserEnabledToggled(checked: Boolean) {
@@ -138,6 +130,12 @@ class DuckChatSettingsViewModel @Inject constructor(
138130
}
139131
}
140132

133+
fun onDuckAiShortcutsClicked() {
134+
viewModelScope.launch {
135+
commandChannel.send(OpenShortcutSettings)
136+
}
137+
}
138+
141139
companion object {
142140
const val DUCK_CHAT_LEARN_MORE_LINK = "https://duckduckgo.com/duckduckgo-help-pages/aichat/"
143141
const val DUCK_CHAT_SEARCH_AI_SETTINGS_LINK = "https://duckduckgo.com/settings?ko=-1#aifeatures"

0 commit comments

Comments
 (0)