diff --git a/.maestro/input_screen/input_screen_chat_mode.yaml b/.maestro/input_screen/input_screen_chat_mode.yaml index 9bb47b39c6a4..feb1f9c6e0d3 100644 --- a/.maestro/input_screen/input_screen_chat_mode.yaml +++ b/.maestro/input_screen/input_screen_chat_mode.yaml @@ -14,9 +14,7 @@ tags: # enable the Input Screen - runFlow: ../shared/open_ai_settings_screen.yaml - tapOn: - id: "trailingSwitch" - childOf: - id: "duckAiInputScreenEnabledToggle" + id: "duckAiInputScreenToggleWithAiImage" - action: back - action: back diff --git a/.maestro/input_screen/input_screen_preference_management.yaml b/.maestro/input_screen/input_screen_preference_management.yaml index 9d4e82a240b3..2b9844dca081 100644 --- a/.maestro/input_screen/input_screen_preference_management.yaml +++ b/.maestro/input_screen/input_screen_preference_management.yaml @@ -20,9 +20,7 @@ tags: # enable the Input Screen - runFlow: ../shared/open_ai_settings_screen.yaml - tapOn: - id: "trailingSwitch" - childOf: - id: "duckAiInputScreenEnabledToggle" + id: "duckAiInputScreenToggleWithAiImage" - action: back - action: back diff --git a/.maestro/input_screen/input_screen_search_mode.yaml b/.maestro/input_screen/input_screen_search_mode.yaml index a9148cd59132..77312593f355 100644 --- a/.maestro/input_screen/input_screen_search_mode.yaml +++ b/.maestro/input_screen/input_screen_search_mode.yaml @@ -14,9 +14,7 @@ tags: # enable the Input Screen - runFlow: ../shared/open_ai_settings_screen.yaml - tapOn: - id: "trailingSwitch" - childOf: - id: "duckAiInputScreenEnabledToggle" + id: "duckAiInputScreenToggleWithAiImage" - action: back - action: back diff --git a/common/common-ui/src/main/res/drawable/ic_check_accent_24.xml b/common/common-ui/src/main/res/drawable/ic_check_accent_24.xml new file mode 100644 index 000000000000..2bbf0cf41b5a --- /dev/null +++ b/common/common-ui/src/main/res/drawable/ic_check_accent_24.xml @@ -0,0 +1,28 @@ + + + + + + diff --git a/common/common-ui/src/main/res/drawable/ic_shape_circle_disabled_24.xml b/common/common-ui/src/main/res/drawable/ic_shape_circle_disabled_24.xml new file mode 100644 index 000000000000..9e1cde8e666e --- /dev/null +++ b/common/common-ui/src/main/res/drawable/ic_shape_circle_disabled_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/common-ui/src/main/res/values/design-experiments-colors.xml b/common/common-ui/src/main/res/values/design-experiments-colors.xml index 9a1da601e848..3a8554671e45 100644 --- a/common/common-ui/src/main/res/values/design-experiments-colors.xml +++ b/common/common-ui/src/main/res/values/design-experiments-colors.xml @@ -21,6 +21,9 @@ + + @color/blue80 + #27282A #333538 @@ -47,6 +50,9 @@ #B8F9F9F9 + + @color/white + #F2F2F2 #F9F9F9 diff --git a/common/common-ui/src/main/res/values/design-experiments-theming.xml b/common/common-ui/src/main/res/values/design-experiments-theming.xml index 694d1e0a58e0..e102c73a0266 100644 --- a/common/common-ui/src/main/res/values/design-experiments-theming.xml +++ b/common/common-ui/src/main/res/values/design-experiments-theming.xml @@ -40,6 +40,7 @@ @color/controls_fill_primary_dark @color/controls_fill_secondary_dark @color/controls_fill_tertiary_dark + @color/controls_decoration_tertiary_dark ?attr/daxColorBackground @@ -83,6 +84,7 @@ @color/controls_fill_primary_light @color/controls_fill_secondary_light @color/controls_fill_tertiary_light + @color/controls_decoration_tertiary_light ?attr/daxColorBackground diff --git a/common/common-ui/src/main/res/values/design-system-colors.xml b/common/common-ui/src/main/res/values/design-system-colors.xml index 3c6e025e93a6..7ad9847e9c6c 100644 --- a/common/common-ui/src/main/res/values/design-system-colors.xml +++ b/common/common-ui/src/main/res/values/design-system-colors.xml @@ -60,6 +60,7 @@ + @@ -74,6 +75,7 @@ + @@ -191,6 +193,7 @@ #243969EF #2B55CA #1E42A4 + #14307E #D6000000 #99000000 diff --git a/common/common-ui/src/main/res/values/design-system-theming.xml b/common/common-ui/src/main/res/values/design-system-theming.xml index b9652a4ea36e..bfeadbe25b27 100644 --- a/common/common-ui/src/main/res/values/design-system-theming.xml +++ b/common/common-ui/src/main/res/values/design-system-theming.xml @@ -181,6 +181,7 @@ @color/white9 @color/blue30 @color/yellow50 + @color/accent_content_primary_dark @color/white12 @color/white @color/blue30_20 @@ -289,6 +290,7 @@ @color/black9 @color/blue50 @color/yellow50 + @color/accent_content_primary_light @color/black6 @color/gray85 @color/blue50_20 diff --git a/duckchat/duckchat-impl/src/main/AndroidManifest.xml b/duckchat/duckchat-impl/src/main/AndroidManifest.xml index ae960da40092..d653e3642d66 100644 --- a/duckchat/duckchat-impl/src/main/AndroidManifest.xml +++ b/duckchat/duckchat-impl/src/main/AndroidManifest.xml @@ -18,10 +18,15 @@ + + viewModel.onShowDuckChatInMenuToggled(isChecked) + } + + private val addressBarToggleListener = + CompoundButton.OnCheckedChangeListener { _, isChecked -> + viewModel.onShowDuckChatInAddressBarToggled(isChecked) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(binding.root) + + setupToolbar(binding.includeToolbar.toolbar) + + observeViewModel() + } + + private fun observeViewModel() { + viewModel.viewState + .flowWithLifecycle(lifecycle, Lifecycle.State.RESUMED) + .onEach { renderViewState(it) } + .launchIn(lifecycleScope) + } + + private fun renderViewState(viewState: ViewState) { + binding.showDuckAiInMenuToggle.apply { + quietlySetIsChecked(viewState.showInBrowserMenu, menuToggleListener) + } + binding.showDuckAiInAddressBarToggle.apply { + isVisible = viewState.shouldShowAddressBarToggle + quietlySetIsChecked(viewState.showInAddressBar, addressBarToggleListener) + } + } +} diff --git a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/settings/DuckAiShortcutSettingsViewModel.kt b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/settings/DuckAiShortcutSettingsViewModel.kt new file mode 100644 index 000000000000..b311c74fcab5 --- /dev/null +++ b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/settings/DuckAiShortcutSettingsViewModel.kt @@ -0,0 +1,63 @@ +/* + * 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.duckchat.impl.ui.settings + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.duckduckgo.anvil.annotations.ContributesViewModel +import com.duckduckgo.di.scopes.ActivityScope +import com.duckduckgo.duckchat.impl.DuckChatInternal +import javax.inject.Inject +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +@ContributesViewModel(ActivityScope::class) +class DuckAiShortcutSettingsViewModel @Inject constructor( + private val duckChat: DuckChatInternal, +) : ViewModel() { + + data class ViewState( + val showInBrowserMenu: Boolean = false, + val showInAddressBar: Boolean = false, + val shouldShowAddressBarToggle: Boolean = false, + ) + + val viewState = combine( + duckChat.observeShowInBrowserMenuUserSetting(), + duckChat.observeShowInAddressBarUserSetting(), + ) { showInBrowserMenu, showInAddressBar -> + ViewState( + showInBrowserMenu = showInBrowserMenu, + showInAddressBar = showInAddressBar, + shouldShowAddressBarToggle = duckChat.isAddressBarEntryPointEnabled(), + ) + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), ViewState()) + + fun onShowDuckChatInMenuToggled(checked: Boolean) { + viewModelScope.launch { + duckChat.setShowInBrowserMenuUserSetting(checked) + } + } + + fun onShowDuckChatInAddressBarToggled(checked: Boolean) { + viewModelScope.launch { + duckChat.setShowInAddressBarUserSetting(checked) + } + } +} diff --git a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/DuckChatSettingsActivity.kt b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsActivity.kt similarity index 52% rename from duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/DuckChatSettingsActivity.kt rename to duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsActivity.kt index 1a03b4843d30..28cd6736cc84 100644 --- a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/DuckChatSettingsActivity.kt +++ b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsActivity.kt @@ -14,12 +14,16 @@ * limitations under the License. */ -package com.duckduckgo.duckchat.impl.ui +package com.duckduckgo.duckchat.impl.ui.settings +import android.content.Intent +import android.content.res.Configuration import android.os.Bundle import android.view.View import android.widget.CompoundButton +import androidx.core.content.ContextCompat import androidx.core.view.isVisible +import androidx.core.view.updatePadding import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope @@ -27,9 +31,11 @@ import com.duckduckgo.anvil.annotations.ContributeToActivityStarter import com.duckduckgo.anvil.annotations.InjectWith import com.duckduckgo.app.statistics.pixels.Pixel import com.duckduckgo.app.tabs.BrowserNav +import com.duckduckgo.browser.api.ui.BrowserScreens.FeedbackActivityWithEmptyParams import com.duckduckgo.browser.api.ui.BrowserScreens.WebViewActivityWithParams import com.duckduckgo.common.ui.DuckDuckGoActivity import com.duckduckgo.common.ui.spans.DuckDuckGoClickableSpan +import com.duckduckgo.common.ui.store.AppTheme import com.duckduckgo.common.ui.view.addClickableSpan import com.duckduckgo.common.ui.view.gone import com.duckduckgo.common.ui.view.show @@ -39,7 +45,8 @@ import com.duckduckgo.duckchat.api.DuckChatSettingsNoParams import com.duckduckgo.duckchat.impl.R import com.duckduckgo.duckchat.impl.databinding.ActivityDuckChatSettingsBinding import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_SETTINGS_DISPLAYED -import com.duckduckgo.duckchat.impl.ui.DuckChatSettingsViewModel.ViewState +import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.ViewState +import com.duckduckgo.mobile.android.R as CommonR import com.duckduckgo.navigation.api.GlobalActivityStarter import javax.inject.Inject import kotlinx.coroutines.flow.launchIn @@ -57,21 +64,6 @@ class DuckChatSettingsActivity : DuckDuckGoActivity() { viewModel.onDuckChatUserEnabledToggled(isChecked) } - private val inputScreenToggleListener = - CompoundButton.OnCheckedChangeListener { _, isChecked -> - viewModel.onDuckAiInputScreenToggled(isChecked) - } - - private val menuToggleListener = - CompoundButton.OnCheckedChangeListener { _, isChecked -> - viewModel.onShowDuckChatInMenuToggled(isChecked) - } - - private val addressBarToggleListener = - CompoundButton.OnCheckedChangeListener { _, isChecked -> - viewModel.onShowDuckChatInAddressBarToggled(isChecked) - } - @Inject lateinit var globalActivityStarter: GlobalActivityStarter @@ -81,6 +73,9 @@ class DuckChatSettingsActivity : DuckDuckGoActivity() { @Inject lateinit var pixel: Pixel + @Inject + lateinit var appTheme: AppTheme + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -107,21 +102,42 @@ class DuckChatSettingsActivity : DuckDuckGoActivity() { private fun renderViewState(viewState: ViewState) { if (viewState.isRebrandingAiFeaturesEnabled) { + binding.includeToolbar.toolbar.title = getString(R.string.duck_chat_title_rebranding) + binding.duckChatSettingsIcon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_ai_128)) binding.userEnabledDuckChatToggleRebranding.quietlySetIsChecked(viewState.isDuckChatUserEnabled, userEnabledDuckChatToggleListener) binding.duckChatSettingsTitle.setText(R.string.duck_chat_title_rebranding) binding.userEnabledDuckChatToggle.gone() binding.userEnabledDuckChatToggleRebranding.show() - binding.duckChatToggleSettingsTitle.setText(R.string.duck_chat_show_in_heading_rebranding) binding.showDuckChatSearchSettingsLink.setPrimaryText(getString(R.string.duck_chat_assist_settings_title_rebranding)) binding.showDuckChatSearchSettingsLink.setSecondaryText(getString(R.string.duck_chat_assist_settings_description_rebranding)) + + // align content with the main Duck.ai toggle's text + val offset = resources.getDimensionPixelSize(CommonR.dimen.listItemImageContainerSize) + + resources.getDimensionPixelSize(CommonR.dimen.keyline_4) + val orientation = resources.configuration.orientation + binding.duckAiInputScreenToggleContainer.updatePadding( + left = if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + 0 + } else { + offset + }, + ) + binding.duckAiInputScreenDescription.updatePadding(left = offset) + binding.duckAiShortcuts.updatePadding(left = offset) } else { + binding.includeToolbar.toolbar.title = getString(R.string.duck_ai_paid_settings_title) + binding.duckChatSettingsIcon.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.chat_private_128)) binding.userEnabledDuckChatToggle.quietlySetIsChecked(viewState.isDuckChatUserEnabled, userEnabledDuckChatToggleListener) binding.duckChatSettingsTitle.setText(R.string.duck_chat_title) binding.userEnabledDuckChatToggle.show() binding.userEnabledDuckChatToggleRebranding.gone() - binding.duckChatToggleSettingsTitle.setText(R.string.duck_chat_show_in_heading) binding.showDuckChatSearchSettingsLink.setPrimaryText(getString(R.string.duck_chat_assist_settings_title)) binding.showDuckChatSearchSettingsLink.setSecondaryText(getString(R.string.duck_chat_assist_settings_description)) + + val offset = 0 + binding.duckAiInputScreenToggleContainer.updatePadding(left = offset) + binding.duckAiInputScreenDescription.updatePadding(left = offset) + binding.duckAiShortcuts.updatePadding(left = offset) } binding.duckChatSettingsText.addClickableSpan( @@ -139,24 +155,38 @@ class DuckChatSettingsActivity : DuckDuckGoActivity() { ), ) - binding.duckAiInputScreenEnabledToggle.apply { - isVisible = viewState.shouldShowInputScreenToggle - quietlySetIsChecked(viewState.isInputScreenEnabled, inputScreenToggleListener) - } + binding.duckAiInputScreenToggleContainer.isVisible = viewState.shouldShowInputScreenToggle + configureInputScreenToggle( + withoutAi = InputScreenToggleButton.WithoutAi(isActive = !viewState.isInputScreenEnabled, appTheme.isLightModeEnabled()), + withAi = InputScreenToggleButton.WithAi(isActive = viewState.isInputScreenEnabled, appTheme.isLightModeEnabled()), + ) - binding.duckChatToggleSettingsTitle.isVisible = viewState.isDuckChatUserEnabled + binding.duckAiInputScreenDescription.isVisible = viewState.shouldShowInputScreenToggle + binding.duckAiInputScreenDescription.addClickableSpan( + textSequence = getText(R.string.input_screen_user_pref_description), + spans = listOf( + "share_feedback" to object : DuckDuckGoClickableSpan() { + override fun onClick(widget: View) { + viewModel.duckAiInputScreenShareFeedbackClicked() + } + }, + ), + ) - binding.showDuckChatInMenuToggle.apply { - isVisible = viewState.shouldShowAddressBarToggle - quietlySetIsChecked(viewState.showInBrowserMenu, menuToggleListener) - } - binding.showDuckChatInAddressBarToggle.apply { - isVisible = viewState.shouldShowAddressBarToggle - quietlySetIsChecked(viewState.showInAddressBar, addressBarToggleListener) + binding.duckAiShortcuts.isVisible = viewState.shouldShowShortcuts + binding.duckAiShortcuts.setOnClickListener { + viewModel.onDuckAiShortcutsClicked() } + binding.showDuckChatSearchSettingsLink.setOnClickListener { viewModel.duckChatSearchAISettingsClicked() } + binding.duckAiInputScreenWithoutAiContainer.setOnClickListener { + viewModel.onDuckAiInputScreenWithoutAiSelected() + } + binding.duckAiInputScreenWithAiContainer.setOnClickListener { + viewModel.onDuckAiInputScreenWithAiSelected() + } } private fun processCommand(command: DuckChatSettingsViewModel.Command) { @@ -173,6 +203,55 @@ class DuckChatSettingsActivity : DuckDuckGoActivity() { is DuckChatSettingsViewModel.Command.OpenLinkInNewTab -> { startActivity(browserNav.openInNewTab(this@DuckChatSettingsActivity, command.link)) } + + is DuckChatSettingsViewModel.Command.OpenShortcutSettings -> { + val intent = Intent(this, DuckAiShortcutSettingsActivity::class.java) + startActivity(intent) + } + + is DuckChatSettingsViewModel.Command.LaunchFeedback -> { + globalActivityStarter.start(this, FeedbackActivityWithEmptyParams) + } + } + } + + private fun configureInputScreenToggle( + withoutAi: InputScreenToggleButton, + withAi: InputScreenToggleButton, + ) = with(binding) { + val context = this@DuckChatSettingsActivity + duckAiInputScreenToggleWithoutAiImage.setImageDrawable(ContextCompat.getDrawable(context, withoutAi.imageRes)) + duckAiInputScreenToggleWithoutAiCheck.setImageDrawable(ContextCompat.getDrawable(context, withoutAi.checkRes)) + + duckAiInputScreenToggleWithAiImage.setImageDrawable(ContextCompat.getDrawable(context, withAi.imageRes)) + duckAiInputScreenToggleWithAiCheck.setImageDrawable(ContextCompat.getDrawable(context, withAi.checkRes)) + } + + private sealed class InputScreenToggleButton(isActive: Boolean) { + abstract val imageRes: Int + + val checkRes: Int = if (isActive) { + CommonR.drawable.ic_check_accent_24 + } else { + CommonR.drawable.ic_shape_circle_disabled_24 + } + + class WithoutAi(isActive: Boolean, isLightMode: Boolean) : InputScreenToggleButton(isActive) { + override val imageRes: Int = when { + isActive && isLightMode -> R.drawable.searchbox_withoutai_active + isActive && !isLightMode -> R.drawable.searchbox_withoutai_active_dark + !isActive && isLightMode -> R.drawable.searchbox_withoutai_inactive + else -> R.drawable.searchbox_withoutai_inactive_dark + } + } + + class WithAi(isActive: Boolean, isLightMode: Boolean) : InputScreenToggleButton(isActive) { + override val imageRes: Int = when { + isActive && isLightMode -> R.drawable.searchbox_withai_active + isActive && !isLightMode -> R.drawable.searchbox_withai_active_dark + !isActive && isLightMode -> R.drawable.searchbox_withai_inactive + else -> R.drawable.searchbox_withai_inactive_dark + } } } } diff --git a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/DuckChatSettingsViewModel.kt b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsViewModel.kt similarity index 76% rename from duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/DuckChatSettingsViewModel.kt rename to duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsViewModel.kt index 6c0fd7ed984b..2a8ec919d589 100644 --- a/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/DuckChatSettingsViewModel.kt +++ b/duckchat/duckchat-impl/src/main/java/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsViewModel.kt @@ -14,18 +14,18 @@ * limitations under the License. */ -package com.duckduckgo.duckchat.impl.ui +package com.duckduckgo.duckchat.impl.ui.settings import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.duckduckgo.anvil.annotations.ContributesViewModel import com.duckduckgo.app.statistics.pixels.Pixel -import com.duckduckgo.common.ui.experiments.visual.store.ExperimentalThemingDataStore import com.duckduckgo.di.scopes.ActivityScope import com.duckduckgo.duckchat.impl.DuckChatInternal import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName -import com.duckduckgo.duckchat.impl.ui.DuckChatSettingsViewModel.Command.OpenLink -import com.duckduckgo.duckchat.impl.ui.DuckChatSettingsViewModel.Command.OpenLinkInNewTab +import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.Command.OpenLink +import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.Command.OpenLinkInNewTab +import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.Command.OpenShortcutSettings import com.duckduckgo.subscriptions.api.SubscriptionRebrandingFeatureToggle import javax.inject.Inject import kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST @@ -40,7 +40,6 @@ import kotlinx.coroutines.launch class DuckChatSettingsViewModel @Inject constructor( private val duckChat: DuckChatInternal, private val pixel: Pixel, - private val experimentalThemingDataStore: ExperimentalThemingDataStore, private val rebrandingAiFeaturesEnabled: SubscriptionRebrandingFeatureToggle, ) : ViewModel() { @@ -50,28 +49,20 @@ class DuckChatSettingsViewModel @Inject constructor( data class ViewState( val isDuckChatUserEnabled: Boolean = false, val isInputScreenEnabled: Boolean = false, - val showInBrowserMenu: Boolean = false, - val showInAddressBar: Boolean = false, + val shouldShowShortcuts: Boolean = false, val shouldShowInputScreenToggle: Boolean = false, - val shouldShowBrowserMenuToggle: Boolean = false, - val shouldShowAddressBarToggle: Boolean = false, val isRebrandingAiFeaturesEnabled: Boolean = false, ) val viewState = combine( duckChat.observeEnableDuckChatUserSetting(), duckChat.observeInputScreenUserSettingEnabled(), - duckChat.observeShowInBrowserMenuUserSetting(), - duckChat.observeShowInAddressBarUserSetting(), - ) { isDuckChatUserEnabled, isInputScreenEnabled, showInBrowserMenu, showInAddressBar -> + ) { isDuckChatUserEnabled, isInputScreenEnabled -> ViewState( isDuckChatUserEnabled = isDuckChatUserEnabled, isInputScreenEnabled = isInputScreenEnabled, - showInBrowserMenu = showInBrowserMenu, - showInAddressBar = showInAddressBar, + shouldShowShortcuts = isDuckChatUserEnabled, shouldShowInputScreenToggle = isDuckChatUserEnabled && duckChat.isInputScreenFeatureAvailable(), - shouldShowBrowserMenuToggle = isDuckChatUserEnabled, - shouldShowAddressBarToggle = isDuckChatUserEnabled && duckChat.isAddressBarEntryPointEnabled(), isRebrandingAiFeaturesEnabled = rebrandingAiFeaturesEnabled.isAIFeaturesRebrandingEnabled(), ) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), ViewState()) @@ -79,6 +70,8 @@ class DuckChatSettingsViewModel @Inject constructor( sealed class Command { data class OpenLink(val link: String) : Command() data class OpenLinkInNewTab(val link: String) : Command() + data object OpenShortcutSettings : Command() + data object LaunchFeedback : Command() } fun onDuckChatUserEnabledToggled(checked: Boolean) { @@ -92,17 +85,6 @@ class DuckChatSettingsViewModel @Inject constructor( } } - fun onDuckAiInputScreenToggled(checked: Boolean) { - viewModelScope.launch { - if (checked) { - pixel.fire(DuckChatPixelName.DUCK_CHAT_EXPERIMENTAL_ADDRESS_BAR_SETTING_ON) - } else { - pixel.fire(DuckChatPixelName.DUCK_CHAT_EXPERIMENTAL_ADDRESS_BAR_SETTING_OFF) - } - duckChat.setInputScreenUserSetting(checked) - } - } - fun onShowDuckChatInMenuToggled(checked: Boolean) { viewModelScope.launch { if (checked) { @@ -138,6 +120,32 @@ class DuckChatSettingsViewModel @Inject constructor( } } + fun onDuckAiShortcutsClicked() { + viewModelScope.launch { + commandChannel.send(OpenShortcutSettings) + } + } + + fun onDuckAiInputScreenWithoutAiSelected() { + viewModelScope.launch { + pixel.fire(DuckChatPixelName.DUCK_CHAT_EXPERIMENTAL_ADDRESS_BAR_SETTING_OFF) + duckChat.setInputScreenUserSetting(enabled = false) + } + } + + fun onDuckAiInputScreenWithAiSelected() { + viewModelScope.launch { + pixel.fire(DuckChatPixelName.DUCK_CHAT_EXPERIMENTAL_ADDRESS_BAR_SETTING_ON) + duckChat.setInputScreenUserSetting(enabled = true) + } + } + + fun duckAiInputScreenShareFeedbackClicked() { + viewModelScope.launch { + commandChannel.send(Command.LaunchFeedback) + } + } + companion object { const val DUCK_CHAT_LEARN_MORE_LINK = "https://duckduckgo.com/duckduckgo-help-pages/aichat/" const val DUCK_CHAT_SEARCH_AI_SETTINGS_LINK = "https://duckduckgo.com/settings?ko=-1#aifeatures" diff --git a/duckchat/duckchat-impl/src/main/res/drawable/ic_ai_128.xml b/duckchat/duckchat-impl/src/main/res/drawable/ic_ai_128.xml new file mode 100644 index 000000000000..e25a2f97b39c --- /dev/null +++ b/duckchat/duckchat-impl/src/main/res/drawable/ic_ai_128.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withai_active.xml b/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withai_active.xml new file mode 100644 index 000000000000..0b17064478f7 --- /dev/null +++ b/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withai_active.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + diff --git a/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withai_active_dark.xml b/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withai_active_dark.xml new file mode 100644 index 000000000000..8ca722a7cc17 --- /dev/null +++ b/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withai_active_dark.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + diff --git a/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withai_inactive.xml b/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withai_inactive.xml new file mode 100644 index 000000000000..3c548419585f --- /dev/null +++ b/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withai_inactive.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withai_inactive_dark.xml b/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withai_inactive_dark.xml new file mode 100644 index 000000000000..caba46f7a00b --- /dev/null +++ b/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withai_inactive_dark.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withoutai_active.xml b/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withoutai_active.xml new file mode 100644 index 000000000000..d1131427e889 --- /dev/null +++ b/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withoutai_active.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + diff --git a/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withoutai_active_dark.xml b/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withoutai_active_dark.xml new file mode 100644 index 000000000000..08c42a5f0e41 --- /dev/null +++ b/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withoutai_active_dark.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + diff --git a/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withoutai_inactive.xml b/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withoutai_inactive.xml new file mode 100644 index 000000000000..7b31f00c4752 --- /dev/null +++ b/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withoutai_inactive.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + diff --git a/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withoutai_inactive_dark.xml b/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withoutai_inactive_dark.xml new file mode 100644 index 000000000000..fafb85ee5911 --- /dev/null +++ b/duckchat/duckchat-impl/src/main/res/drawable/searchbox_withoutai_inactive_dark.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + diff --git a/duckchat/duckchat-impl/src/main/res/layout/activity_duck_ai_shortcut_settings.xml b/duckchat/duckchat-impl/src/main/res/layout/activity_duck_ai_shortcut_settings.xml new file mode 100644 index 000000000000..f05eb94cea8d --- /dev/null +++ b/duckchat/duckchat-impl/src/main/res/layout/activity_duck_ai_shortcut_settings.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/layout/activity_duck_chat_settings.xml b/duckchat/duckchat-impl/src/main/res/layout/activity_duck_chat_settings.xml index ff7563496e5a..329910efbc6a 100644 --- a/duckchat/duckchat-impl/src/main/res/layout/activity_duck_chat_settings.xml +++ b/duckchat/duckchat-impl/src/main/res/layout/activity_duck_chat_settings.xml @@ -19,7 +19,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".ui.DuckChatSettingsActivity"> + tools:context=".ui.settings.DuckChatSettingsActivity"> - + android:layout_marginTop="@dimen/keyline_4" + android:layout_marginHorizontal="@dimen/keyline_4"> + + + + + + + + + + + + + + + + + + + + + + - - - + android:layout_marginHorizontal="@dimen/keyline_4" + android:layout_marginTop="@dimen/keyline_2" + android:layout_marginBottom="@dimen/keyline_4" + android:text="@string/input_screen_user_pref_description" + app:textType="secondary" + app:typography="body2" /> - + android:visibility="gone" + app:primaryText="@string/duck_ai_shortcut_settings_title" + app:secondaryText="@string/duck_ai_shortcut_settings_description" /> Duck.ai е незадължителна функция, чрез която можете да разговаряте анонимно с популярни AI чат модели от трети страни. Чатовете Ви не се използват за обучение на AI.\nНаучете повече Активиране на Duck.ai - Бързи Връзки Меню на браузъра Адресна лента Настройки на Assist @@ -50,8 +49,6 @@ Търсене или въвеждане на адрес Duck.ai Попитай Duck.ai - Експериментална адресна лента - Потърсете в мрежата или попитайте Duck.ai директно от адресната лента Duck.ai @@ -64,8 +61,6 @@ Функции с изкуствен интелект Функциите на DuckDuckGo AI са поверителни и незадължителни.Вашите данни не се използват за обучение на AI.\nНаучете повече Duck.ai - Анонимен чат с популярни AI модели на трети страни - Преки пътища за Duck.ai Настройки за Assist при търсене Изберете колко често искате да се появяват отговори, подпомагани от изкуствен интелект, в търсенето \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-cs/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-cs/strings-duckchat.xml index 0c4e95df7165..5ef8d96db45e 100644 --- a/duckchat/duckchat-impl/src/main/res/values-cs/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-cs/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai je volitelná funkce, která umožňuje anonymně chatovat s populárními AI chatovacími modely třetích stran. Tvoje chaty neslouží k trénování umělé inteligence.\nDalší informace Povolit službu Duck.ai - Zkratky Menu prohlížeče Adresní řádek Nastavení funkce Assist @@ -50,8 +49,6 @@ Vyhledejte nebo zadejte adresu Duck.ai Zeptej se Duck.ai - Experimentální adresní řádek - Hledej na webu nebo se zeptej Duck.ai přímo v adresním řádku. Duck.ai @@ -64,8 +61,6 @@ Funkce AI Funkce AI na DuckDuckGo jsou soukromé a volitelné.Tvoje data se nepoužívají k trénování umělé inteligence.\nDalší informace Duck.ai - S populárními AI modely třetích stran můžeš chatovat anonymně - Zkratky Duck.ai Nastavení vyhledávání s funkcí Assist Vyber, jak často chceš, aby se odpovědi s podporou umělé inteligence zobrazovaly ve tvých vyhledáváních \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-da/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-da/strings-duckchat.xml index ee21ac651ae9..da48fa695fe5 100644 --- a/duckchat/duckchat-impl/src/main/res/values-da/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-da/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai er en valgfri funktion, der giver dig mulighed for at chatte anonymt med populære tredjeparts AI-chatmodeller. Dine chats bruges ikke til at træne AI.\nLæs mere Aktiver Duck.ai - Genveje Browsermenu Adresselinje Assist-indstillinger @@ -50,8 +49,6 @@ Søg eller indtast adresse Duck.ai Spørg Duck.ai - Eksperimentel adresselinje - Søg på nettet eller spørg Duck.ai direkte fra adresselinjen Duck.ai @@ -64,8 +61,6 @@ AI-funktioner DuckDuckGo AI-funktioner er private og valgfrie.\nDine data bruges ikke til at træne AI.\nLæs mere Duck.ai - Chat anonymt med populære tredjeparts AI-chatmodeller - Duck.ai-genveje Indstillinger for Search Assist Vælg, hvor ofte du vil have AI-assisterede svar til at optræde i dine søgninger \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-de/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-de/strings-duckchat.xml index d8fce7407d23..6f47e9c615bc 100644 --- a/duckchat/duckchat-impl/src/main/res/values-de/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-de/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai ist eine optionale Funktion, mit der du anonym mit beliebten KI-Chatmodellen von Drittanbietern chatten kannst. Deine Chats werden nicht zum Trainieren von KI verwendet.\nMehr erfahren Aktiviere Duck.ai - Shortcuts Browsermenü Adresszeile Assist-Einstellungen @@ -50,8 +49,6 @@ Adresse suchen oder eingeben Duck.ai Frag Duck.ai - Experimentelle Adressleiste - Suche im Web oder frag Duck.ai direkt über die Adressleiste Duck.ai @@ -64,8 +61,6 @@ KI-Funktionen Die KI-Funktionen von DuckDuckGo sind privat und optional.\nIhre Daten werden nicht zum Trainieren von KI verwendet.\nMehr erfahren Duck.ai - Chatte anonym mit beliebten KI-Chatmodellen von Drittanbietern - Duck.ai-Shortcuts Such-Assist-Einstellungen Du kannst auswählen, wie oft KI-gestützte Antworten in deinen Suchergebnissen angezeigt werden sollen. \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-el/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-el/strings-duckchat.xml index a9a32a6729a3..f9dfe1e6fe78 100644 --- a/duckchat/duckchat-impl/src/main/res/values-el/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-el/strings-duckchat.xml @@ -39,7 +39,6 @@ Το Duck.ai αποτελεί προαιρετική λειτουργία που σας επιτρέπει να συνομιλείτε ανώνυμα με δημοφιλή μοντέλα τρίτων για συνομιλία μέσω τεχνητής νοημοσύνης. Οι συνομιλίες σας δεν χρησιμοποιούνται για την εκπαίδευση τεχνητής συνομιλίας.\nΜάθετε περισσότερα Ενεργοποίηση του Duck.ai - Συντομευσεις Μενού προγράμματος περιήγησης Γραμμή διευθύνσεων Ρυθμίσεις Assist @@ -50,8 +49,6 @@ Αναζήτηση ή εισαγωγή διεύθυνσης Duck.ai Ρωτήστε το Duck.ai - Πειραματική γραμμή διευθύνσεων - Κάντε αναζήτηση στο διαδίκτυο ή ρωτήστε το Duck.ai απευθείας από τη Γραμμή διευθύνσεων Duck.ai @@ -64,8 +61,6 @@ Λειτουργίες τεχνητής νοημοσύνης Οι λειτουργίες τεχνητής νοημοσύνης του DuckDuckGo είναι ιδιωτικές και προαιρετικές.Τα δεδομένα σας δεν χρησιμοποιούνται για εκπαίδευση της τεχνητής νοημοσύνης.\nΜάθετε περισσότερα Duck.ai - Συνομίλησε ανώνυμα με δημοφιλή μοντέλα συνομιλίας μέσω τεχνητής νοημοσύνης, τρίτων μερών - Συντομεύσεις Duck.ai Ρυθμίσεις αναζήτησης Assist Επιλέξτε πόσο συχνά θέλετε να εμφανίζονται οι απαντήσεις που υποστηρίζονται από τεχνητή νοημοσύνη στις αναζητήσεις σας \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-es/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-es/strings-duckchat.xml index d01cb3e678b6..07b538032318 100644 --- a/duckchat/duckchat-impl/src/main/res/values-es/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-es/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai es una función opcional que te permite chatear de forma anónima con modelos populares de chat de IA de terceros. Tus chats no se utilizan para entrenar la IA.\nMás información Activa Duck.ai - Accesos directos Menú del navegador Barra de direcciones Ajustes de Assist @@ -50,8 +49,6 @@ Buscar o introducir dirección Duck.ai Pregúntale a Duck.ai - Barra de direcciones experimental - Busca en la web o pregúntale a Duck.ai directamente desde la barra de direcciones Duck.ai @@ -64,8 +61,6 @@ Características de la IA Las funciones de IA de DuckDuckGo son privadas y opcionales.Tus datos no se utilizan para entrenar la IA.\nMás información Duck.ai - Chatea de forma anónima con modelos de chat de IA populares de terceros - Atajos de Duck.ai Configuración de Asistente de búsqueda Elige con qué frecuencia deseas que las respuestas asistidas por IA aparezcan en tus búsquedas \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-et/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-et/strings-duckchat.xml index 6e60eb7c3c32..0f5c674e2233 100644 --- a/duckchat/duckchat-impl/src/main/res/values-et/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-et/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai on valikuline funktsioon, mis võimaldab anonüümselt vestelda populaarsete kolmanda poole AI vestlusmudelitega. Teie vestlusi ei kasutata tehisintellekti treenimiseks.\nLisateave Luba Duck.ai - Otseteed Brauseri menüü Aadressiriba Assisti seaded @@ -50,8 +49,6 @@ Otsi või sisesta aadress Duck.ai Küsi Duck.ai käest - Eksperimentaalne aadressiriba - Otsi veebist või küsi Duck.ai-lt otse aadressiribal Duck.ai @@ -64,8 +61,6 @@ AI funktsioonid DuckDuckGo tehisintellekti funktsioonid on privaatsed ja valikulised.Sinu andmeid ei kasutata tehisintellekti treenimiseks.\nLisateave Duck.ai - Vestle anonüümselt populaarsete kolmanda osapoole tehisintellekti vestlusmudelitega - Duck.ai otseteed Otsingu Assist seaded Vali, kui tihti soovid, et AI-toega vastused ilmuksid sinu otsingutesse \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-fi/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-fi/strings-duckchat.xml index 63323c07bf4a..ff2966177f6f 100644 --- a/duckchat/duckchat-impl/src/main/res/values-fi/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-fi/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai on valinnainen ominaisuus, jonka avulla voit keskustella anonyymisti suosittujen kolmannen osapuolen tekoälykeskustelumallien kanssa. Keskustelujasi ei käytetä tekoälyn kouluttamiseen.\nLue lisää Ota Duck.ai käyttöön - Pikavalinnat Selainvalikko Osoitekenttä Assist-asetukset @@ -50,8 +49,6 @@ Hae tai anna osoite Duck.ai Kysy Duck.ai:lta - Kokeellinen osoitekenttä - Hae verkosta tai kysy Duck.ai:lta kirjoittamalla suoraan osoiteriville Duck.ai @@ -64,8 +61,6 @@ Tekoälyominaisuudet DuckDuckGon tekoälyominaisuudet ovat yksityisiä ja valinnaisia.\nTietojasi ei käytetä tekoälyn kouluttamiseen.\nLue lisää Duck.ai - Keskustele nimettömästi suosittujen kolmannen osapuolen tekoälykeskustelumallien kanssa - Duck.ai-pikakuvakkeet Search Assist -asetukset Valitse, kuinka usein haluat tekoälyavusteisten vastausten näkyvän hauissasi \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-fr/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-fr/strings-duckchat.xml index 669f5a3b10b0..3b7956d3a77b 100644 --- a/duckchat/duckchat-impl/src/main/res/values-fr/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-fr/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai est une fonctionnalité optionnelle qui vous permet de discuter anonymement avec des modèles de chat IA tiers populaires. Vos discussions ne servent pas à entraîner l’IA.\nEn savoir plus Activer Duck.ai - Raccourcis Menu du navigateur Barre d\'adresse Paramètres d\'Assist @@ -50,8 +49,6 @@ Rechercher ou saisir une adresse Duck.ai Demandez à Duck.ai - Barre d\'adresse expérimentale - Effectuez une recherche sur le Web ou interrogez Duck.ai directement à partir de la barre d’adresse Duck.ai @@ -64,8 +61,6 @@ Fonctionnalités de l\'IA Les fonctionnalités de DuckDuckGo AI sont privées et facultatives.Vos données ne servent pas à entraîner l’IA.\nEn savoir plus Duck.ai - Discutez anonymement avec des modèles de chat IA tiers populaires - Raccourcis Duck.ai Paramètres de recherche Assist Choisissez la fréquence à laquelle vous souhaitez que les réponses assistées par l\'IA apparaissent dans vos recherches \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-hr/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-hr/strings-duckchat.xml index c7b1b8be5efe..8b716c3fab42 100644 --- a/duckchat/duckchat-impl/src/main/res/values-hr/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-hr/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai izborna je značajka koja ti omogućuje anonimno čavrljanje s popularnim AI modelima \'chata\' trećih strana. Tvoja čavrljanja se ne koriste za obuku AI-a.\nSaznaj više Omogući Duck.ai - Prečaci Izbornik preglednika Adresna traka Postavke Assista @@ -50,8 +49,6 @@ Pretraži ili unesi adresu Duck.ai Pitaj Duck.ai - Eksperimentalna adresna traka - Pretraži mrežu ili pitaj Duck.ai izravno iz adresne trake Duck.ai @@ -64,8 +61,6 @@ AI značajke DuckDuckGo AI značajke privatne su i neobavezne.Tvoji podaci se ne koriste za obuku umjetne inteligencije.\nSaznaj više Duck.ai - Anonimno čavrljaj s popularnim modelima umjetne inteligencije trećih strana - Duck.ai prečaci Pretraži postavke Assista Odaberi koliko često želiš da se odgovori potpomognuti umjetnom inteligencijom pojavljuju u tvojim pretraživanjima \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-hu/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-hu/strings-duckchat.xml index 24988ad2940f..2ce86c4e1f6f 100644 --- a/duckchat/duckchat-impl/src/main/res/values-hu/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-hu/strings-duckchat.xml @@ -39,7 +39,6 @@ A Duck.ai egy opcionális funkció, amely lehetővé teszi a harmadik féltől származó népszerű AI-csevegési modellekkel való névtelen csevegést. A csevegéseidet nem használjuk fel az AI tanítására.\nTovábbi részletek Duck.ai engedélyezése - Gyorsparancsok Böngészési menü Címsor Assist-beállítások @@ -50,8 +49,6 @@ Keresés vagy cím megadása Duck.ai Duck.ai kérdezése - Kísérleti címsor - Keresés a weben vagy a Duck.ai megkérdezése közvetlenül a címsorból Duck.ai @@ -64,8 +61,6 @@ AI funkciók A DuckDuckGo AI-funkciói privátak és opcionálisak.\nAz adataidat nem használjuk fel az AI tanítására.\nTovábbi részletek Duck.ai - Névtelen csevegés harmadik féltől származó népszerű AI-modellekkel - Duck.ai-gyorsparancsok Keresési asszisztens beállításai Válaszd ki, milyen gyakran szeretnél mesterséges intelligenciával generált válaszokat látni a kereséseid során \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-it/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-it/strings-duckchat.xml index f4c6e7ad86aa..2efad85de650 100644 --- a/duckchat/duckchat-impl/src/main/res/values-it/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-it/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai è una funzionalità opzionale che consente di chattare in forma anonima con i più diffusi modelli di chat AI di terze parti. Le chat non vengono usate per addestrare l\'IA.\nScopri di più Abilita Duck.ai - Scorciatoie Menu del browser Barra degli indirizzi Impostazioni di Assist @@ -50,8 +49,6 @@ Cerca o digita l\'indirizzo Duck.ai Chiedi a Duck.ai - Barra degli indirizzi sperimentale - Cerca sul web o chiedi a Duck.ai direttamente dalla barra degli indirizzi Duck.ai @@ -64,8 +61,6 @@ Funzionalità IA Le funzionalità AI di DuckDuckGo sono private e opzionali.I tuoi dati non vengono usati per addestrare l\'IA.\nScopri di più Duck.ai - Chatta in forma anonima con i più diffusi modelli di chat AI di terze parti - Scorciatoie Duck.ai Impostazioni di Search Assist Scegli la frequenza con cui vuoi che le risposte assistite dall\'intelligenza artificiale appaiano nelle tue ricerche \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-lt/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-lt/strings-duckchat.xml index 3d587326ae12..6e2e71c86755 100644 --- a/duckchat/duckchat-impl/src/main/res/values-lt/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-lt/strings-duckchat.xml @@ -39,7 +39,6 @@ „Duck.ai“ yra pasirenkama funkcija, leidžianti anonimiškai kalbėtis su populiariais trečiųjų šalių DI pokalbių modeliais. Tavo pokalbiai nenaudojami dirbtinio intelekto mokymui.\nSužinok daugiau Įjungti „Duck.ai“ - Šaukiniai Naršyklės meniu Adreso juosta „Assist“ nustatymai @@ -50,8 +49,6 @@ Ieškoti arba įvesti adresą Duck.ai Paklausk „Duck.ai“ - Eksperimentinė adreso juosta - Ieškok internete arba klausk „Duck.ai“ tiesiai iš adreso juostos Duck.ai @@ -64,8 +61,6 @@ DI funkcijos „DuckDuckGo“ DI funkcijos yra privačios ir neprivalomos.Nenaudojame jūsų duomenų dirbtinio intelekto mokymui.\nSužinokite daugiau Duck.ai - Kalbėkitės anonimiškai su populiariais trečiųjų šalių DI pokalbių modeliais - „Duck.ai“ spartieji klavišai „Assist“ paieškos nustatymai Pasirink, kaip dažnai nori, kad tavo paieškose būtų rodomi DI generuojami atsakymai \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-lv/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-lv/strings-duckchat.xml index 3f0300297722..e9434cc1bcf5 100644 --- a/duckchat/duckchat-impl/src/main/res/values-lv/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-lv/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai ir papildu līdzeklis, kas ļauj anonīmi tērzēt ar populāriem trešo pušu Ml tērzēšanas modeļiem. Tavas tērzēšanas sarunas netiek izmantotas AMI apmācībai.\nUzzini vairāk Iespējot Duck.ai - Saīsnes Pārlūka izvēlne Adreses josla Assist iestatījumi @@ -50,8 +49,6 @@ Meklē vai ievadi adresi Duck.ai Pajautā Duck.ai - Eksperimentālā adreses josla - Meklē tīmeklī vai jautā Duck.ai tieši adrešu joslā Duck.ai @@ -64,8 +61,6 @@ Mākslīgā intelekta funkcijas DuckDuckGo MI funkcijas ir privātas un nav obligātas.Tavi dati netiek izmantoti mākslīgā intelekta apmācībai.\nUzzini vairāk Duck.ai - Tērzē anonīmi ar populāriem trešo personu MI tērzēšanas modeļiem - Duck.ai īsceļi Meklēšanas Assist iestatījumi Izvēlies, cik bieži tu vēlies, lai MI atbalstītās atbildes parādītos tavos meklējumos. \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-nb/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-nb/strings-duckchat.xml index f1b37dafc657..cb89f9793bd6 100644 --- a/duckchat/duckchat-impl/src/main/res/values-nb/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-nb/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai er en valgfri funksjon der du kan chatte anonymt med populære tredjeparts AI-chat-modeller. Chattene dine brukes ikke til å trene AI.\nLes mer Aktiver Duck.ai - Snarveier Nettlesermeny Adressefelt Assist-innstillinger @@ -50,8 +49,6 @@ Søk eller skriv inn adresse Duck.ai Spør Duck.ai - Eksperimentell adresselinje - Søk på nettet eller spør Duck.ai direkte fra adressefeltet Duck.ai @@ -64,8 +61,6 @@ AI-funksjoner AI-funksjonene i DuckDuckGo er private og valgfrie.\nDataene dine brukes ikke til å trene AI.\nLes mer Duck.ai - Chat anonymt med populære tredjeparts AI-chat-modeller - Duck.ai-snarveier Innstillinger for Search Assist Velg hvor ofte du vil at AI-assisterte svar skal vises i søkene dine \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-nl/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-nl/strings-duckchat.xml index 7064e0d30285..f03d8a563bf5 100644 --- a/duckchat/duckchat-impl/src/main/res/values-nl/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-nl/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai is een optionele functie waarmee je anoniem kunt chatten met populaire AI-chatmodellen van derden. Je chats worden niet gebruikt om AI te trainen.\nMeer informatie Duck.ai inschakelen - Sneltoetsen Browsermenu Adresbalk Assist-instellingen @@ -50,8 +49,6 @@ Zoek of voer een adres in Duck.ai Vraag stellen aan Duck.ai - Experimentele adresbalk - Zoeken op het web of direct een vraag stellen aan Duck.ai vanuit de adresbalk Duck.ai @@ -64,8 +61,6 @@ AI-functies DuckDuckGo AI-functies zijn privé en optioneel.\nJe gegevens worden niet gebruikt om AI te trainen.\nMeer informatie Duck.ai - Chat anoniem met populaire AI-chatmodellen van derden - Duck.ai-snelkoppelingen Instellingen voor zoek Assist Kies hoe vaak antwoorden op basis van AI in je zoekresultaten moeten worden weergegeven \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-pl/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-pl/strings-duckchat.xml index 9c51c94543f1..93ce8c3afe11 100644 --- a/duckchat/duckchat-impl/src/main/res/values-pl/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-pl/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai to opcjonalna funkcja, która umożliwia prowadzenie anonimowych rozmów z popularnymi modelami czatu AI innych firm. Twoje czaty nie są używane do trenowania AI.\nDowiedz się więcej Włącz Duck.ai - Skróty Menu przeglądarki Pasek adresu Ustawienia Assist @@ -50,8 +49,6 @@ Wyszukaj lub wprowadź adres Duck.ai Zapytaj Duck.ai - Eksperymentalny Pasek Adresu - Wyszukaj w Internecie lub zapytaj Duck.ai bezpośrednio z paska adresu Duck.ai @@ -64,8 +61,6 @@ Funkcje AI Funkcje sztucznej inteligencji DuckDuckGo są prywatne i opcjonalne.Twoje dane nie są używane do trenowania AI.\nDowiedz się więcej Duck.ai - Czatuj anonimowo z popularnymi modelami czatu AI innych firm - Skróty Duck.ai Ustawienia wyszukiwania Assist Wybierz, jak często w wyszukiwaniach mają pojawiać się odpowiedzi wspomagane przez sztuczną inteligencję \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-pt/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-pt/strings-duckchat.xml index 1c341262469a..7b5cc8ecb1f6 100644 --- a/duckchat/duckchat-impl/src/main/res/values-pt/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-pt/strings-duckchat.xml @@ -39,7 +39,6 @@ O Duck.ai é uma funcionalidade opcional que te permite conversar anonimamente com modelos de chat de IA populares de terceiros. As tuas conversas não são utilizadas para treinar a IA.\nSabe mais Ativar Duck.ai - Atalhos Menu do navegador Barra de endereço Definições do Assist @@ -50,8 +49,6 @@ Pesquisar ou inserir endereço Duck.ai Pergunta ao Duck.ai - Barra de endereços experimental - Pesquise na web ou pergunte diretamente ao Duck.ai na barra de endereços Duck.ai @@ -64,8 +61,6 @@ Funcionalidades de IA As funcionalidades de IA do DuckDuckGo são privadas e opcionais.Os teus dados não são utilizados para treinar a IA.\nSabe mais Duck.ai - Conversa anonimamente com modelos de chat com IA populares de terceiros - Atalhos Duck.ai Definições do Assist de pesquisa Escolhe a frequência com que queres que as respostas assistidas por IA apareçam nas suas pesquisas \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-ro/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-ro/strings-duckchat.xml index f991e1a26a1c..802af54372ab 100644 --- a/duckchat/duckchat-impl/src/main/res/values-ro/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-ro/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai este o caracteristică opțională care îți permite să discuți anonim cu modele populare de chat AI de la terțe părți. Chaturile tale nu sunt folosite pentru a antrena AI.\nAflă mai multe Activează Duck.ai - Scurtături Meniu browser Bara de adrese Setări Assist @@ -50,8 +49,6 @@ Caută sau introdu adresa Duck.ai Întreabă Duck.ai - Bară de adrese experimentală - Caută pe web sau întreabă Duck.ai direct din bara de adrese Duck.ai @@ -64,8 +61,6 @@ Caracteristici AI Funcțiile IA ale DuckDuckGo sunt private și opționale.Datele tale nu sunt folosite pentru a antrena IA.\nAflă mai multe Duck.ai - Discută anonim cu modele populare de chat IA de la terțe părți - Comenzi rapide Duck.ai Setări Search Assist Alege cât de des vrei să apară răspunsurile asistate de IA în căutările tale \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-ru/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-ru/strings-duckchat.xml index 560313e2685a..9ad4a742345d 100644 --- a/duckchat/duckchat-impl/src/main/res/values-ru/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-ru/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai — дополнительная функция, позволяющая анонимно беседовать с популярными сторонними чат-моделями на базе искусственного интеллекта. Содержимое чатов не применяется для обучения ИИ.\nПодробнее... Включить Duck.ai - Ярлыки Меню браузера Адресная строка Настройки Assist @@ -50,8 +49,6 @@ Введите поисковый запрос или адрес сайта Duck.ai Спросить у Duck.ai - Пробная адресная строка - Выполнить поиск в интернете или задать вопрос ИИ-ассистенту Duck.ai можно прямо из адресной строки. Duck.ai @@ -64,8 +61,6 @@ Функции ИИ DuckDuckGo AI — дополнительный конфиденциальный сервис.\nВаши данные не применяются для обучения ИИ.\nПодробнее... Duck.ai - Анонимные чаты с популярными сторонними ИИ-моделями - Ярлыки Duck.ai Настройки Search Assist Выберите, как часто вы хотите видеть ответы от ИИ в результатах поиска. \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-sk/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-sk/strings-duckchat.xml index f9fe93028250..052f7286a283 100644 --- a/duckchat/duckchat-impl/src/main/res/values-sk/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-sk/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai je voliteľná funkcia, ktorá ti umožňuje anonymne chatovať s obľúbenými modelmi chatu s umelou inteligenciou tretích strán. Tvoje chaty sa nepoužívajú na trénovanie AI.\nViac informácií Povoliť Duck.ai - Skratky Ponuka prehliadača Riadok adresy Nastavenia Assist @@ -50,8 +49,6 @@ Vyhľadajte alebo zadajte adresu Duck.ai Spýtaj sa Duck.ai - Experimentálny riadok adresy - Vyhľadaj na webe alebo sa opýtaj Duck.ai priamo z riadku pre adresu Duck.ai @@ -64,8 +61,6 @@ Funkcie AI Funkcie DuckDuckGo AI sú súkromné a voliteľné.\nTvoje údaje sa nepoužívajú na trénovanie umelej inteligencie.\nViac informácií Duck.ai - Chatuj anonymne s populárnymi AI modelmi chatu tretích strán - Skratky Duck.ai Nastavenia Assist vyhľadávania Vyber, ako často chceš, aby sa vo vyhľadávaní zobrazovali odpovede podporované AI \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-sl/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-sl/strings-duckchat.xml index 2e6928bfbabb..0157be7f17e8 100644 --- a/duckchat/duckchat-impl/src/main/res/values-sl/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-sl/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai je izbirna funkcija, ki vam omogoča anonimen klepet s priljubljenimi modeli za klepet z umetno inteligenco drugih ponudnikov. Vaši klepeti se ne uporabljajo za usposabljanje umetne inteligence.\nVeč o tem Omogoči Duck.ai - Bližnjice Meni brskalnika Naslovna vrstica Nastavitve modula Assist @@ -50,8 +49,6 @@ Poišči ali vnesi naslov Duck.ai Vprašajte Duck.ai - Eksperimentalna naslovna vrstica - Išči po spletu ali vprašaj Duck.ai neposredno iz naslovne vrstice Duck.ai @@ -64,8 +61,6 @@ Lastnosti UI Funkcije umetne inteligence DuckDuckGo so zasebne in izbirne.\nVaši podatki se ne uporabljajo za usposabljanje umetne inteligence.\nVeč o tem Duck.ai - Anonimno klepetajte s priljubljenimi modeli umetne inteligence tretjih oseb - Bližnjice do Duck.ai Nastavitve modula Search Assist Izberite, kako pogosto želite, da se odgovori s pomočjo umetne inteligence prikazujejo v vaših iskanjih \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-sv/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-sv/strings-duckchat.xml index 83744e1fd950..cabcab2935e4 100644 --- a/duckchat/duckchat-impl/src/main/res/values-sv/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-sv/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai är en valfri funktion som låter dig chatta anonymt med populära AI-chattmodeller från tredje part. Dina chattar används inte för att träna AI.\nLäs mer Aktivera Duck.ai - Genvägar Webbläsarmeny Adressfält Assist-inställningar @@ -50,8 +49,6 @@ Sök eller ange adress Duck.ai Fråga Duck.ai - Experimentell adressfält - Sök på webben eller fråga Duck.ai direkt i adressfältet Duck.ai @@ -64,8 +61,6 @@ AI-funktioner DuckDuckGos AI-funktioner är privata och valfria.\nDina data används inte för att träna AI.\nLäs mer Duck.ai - Chatta anonymt med populära AI-chattmodeller från tredje part - Duck.ai-genvägar Inställningar för Search Assist Välj hur ofta du vill att AI-assisterade svar ska visas i dina sökningar \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values-sw600dp/dimens.xml b/duckchat/duckchat-impl/src/main/res/values-sw600dp/dimens.xml new file mode 100644 index 000000000000..440a2c1ae035 --- /dev/null +++ b/duckchat/duckchat-impl/src/main/res/values-sw600dp/dimens.xml @@ -0,0 +1,19 @@ + + + + 160dp + diff --git a/duckchat/duckchat-impl/src/main/res/values-tr/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values-tr/strings-duckchat.xml index 1bae6aa12af8..29d7fbfee455 100644 --- a/duckchat/duckchat-impl/src/main/res/values-tr/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values-tr/strings-duckchat.xml @@ -39,7 +39,6 @@ Duck.ai, popüler 3. taraf yapay zeka sohbet modelleriyle anonim olarak sohbet etmenizi sağlayan isteğe bağlı bir özelliktir. Sohbetler yapay zekayı eğitmek için kullanılmaz.\nDaha Fazla Bilgi Duck.ai\'ı etkinleştir - Kısayollar Tarayıcı menüsü Adres Çubuğu Assist Ayarları @@ -50,8 +49,6 @@ Adres ara veya gir Duck.ai Duck.ai\'a sor - Deneysel Adres Çubuğu - Web\'de arama yapın veya doğrudan Adres Çubuğundan Duck.ai\'a sorun Duck.ai @@ -64,8 +61,6 @@ AI Özellikleri DuckDuckGo AI özellikleri gizli ve isteğe bağlıdır.\nVerileriniz yapay zekayı eğitmek için kullanılmaz.\nDaha Fazla Bilgi Duck.ai - Popüler üçüncü taraf AI sohbet modelleriyle anonim olarak sohbet edin - Duck.ai Kısayolları Search Assist Ayarları Yapay zeka destekli yanıtların aramalarınızda ne sıklıkta görünmesini istediğinizi seçin \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values/dimens.xml b/duckchat/duckchat-impl/src/main/res/values/dimens.xml index 1d31b9523d65..67f1d03b2f99 100644 --- a/duckchat/duckchat-impl/src/main/res/values/dimens.xml +++ b/duckchat/duckchat-impl/src/main/res/values/dimens.xml @@ -19,4 +19,5 @@ 34dp 2dp 5dp + 16dp \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/main/res/values/strings-duckchat.xml b/duckchat/duckchat-impl/src/main/res/values/strings-duckchat.xml index 70fc8c42e654..a9e4102e8c2f 100644 --- a/duckchat/duckchat-impl/src/main/res/values/strings-duckchat.xml +++ b/duckchat/duckchat-impl/src/main/res/values/strings-duckchat.xml @@ -38,19 +38,21 @@ Duck.ai is an optional feature that lets you chat anonymously with popular 3rd-party Al chat models. Your chats are not used to train AI.\nLearn More Enable Duck.ai - Shortcuts Browser Menu Address Bar Assist Settings Choose how often you want AI-assisted answers to appear in your searches + Duck.ai Shortcuts + Choose which Duck.ai shortcuts to display Search Search or enter address Duck.ai Ask Duck.ai - Experimental Address Bar - Search the web or ask Duck.ai directly from the Address Bar + Search Only\n(Default) + Search & Duck.ai\n(Experimental) + Try our experimental option to ask Duck.ai directly from the Address Bar. Share Feedback Duck.ai @@ -63,8 +65,7 @@ AI Features DuckDuckGo AI features are private and optional.\nYour data is not used to train AI.\nLearn More Duck.ai - Chat anonymously with popular 3rd-party AI chat models - Duck.ai Shortcuts + Chat privately with popular 3rd-party AI models Search Assist Settings Choose how often you want AI-Assisted answers to appear in your searches \ No newline at end of file diff --git a/duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/ui/settings/DuckAiShortcutSettingsViewModelTest.kt b/duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/ui/settings/DuckAiShortcutSettingsViewModelTest.kt new file mode 100644 index 000000000000..cfa5409cb2ee --- /dev/null +++ b/duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/ui/settings/DuckAiShortcutSettingsViewModelTest.kt @@ -0,0 +1,111 @@ +/* + * 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.duckchat.impl.ui.settings + +import app.cash.turbine.test +import com.duckduckgo.common.test.CoroutineTestRule +import com.duckduckgo.duckchat.impl.DuckChatInternal +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class DuckAiShortcutSettingsViewModelTest { + + @get:Rule + @Suppress("unused") + val coroutineRule = CoroutineTestRule() + + private lateinit var testee: DuckAiShortcutSettingsViewModel + + private val duckChat: DuckChatInternal = mock() + + @Before + fun setUp() = runTest { + whenever(duckChat.observeShowInBrowserMenuUserSetting()).thenReturn(flowOf(false)) + whenever(duckChat.observeShowInAddressBarUserSetting()).thenReturn(flowOf(false)) + testee = DuckAiShortcutSettingsViewModel(duckChat) + } + + @Test + fun whenViewModelIsCreatedAndShowInBrowserIsEnabledThenEmitEnabled() = runTest { + whenever(duckChat.observeShowInBrowserMenuUserSetting()).thenReturn(flowOf(true)) + testee = DuckAiShortcutSettingsViewModel(duckChat) + + testee.viewState.test { + assertTrue(awaitItem().showInBrowserMenu) + } + } + + @Test + fun whenViewModelIsCreatedAndShowInBrowserIsDisabledThenEmitDisabled() = runTest { + whenever(duckChat.observeShowInBrowserMenuUserSetting()).thenReturn(flowOf(false)) + testee = DuckAiShortcutSettingsViewModel(duckChat) + + testee.viewState.test { + assertFalse(awaitItem().showInBrowserMenu) + } + } + + @Test + fun whenViewModelIsCreatedAndShowInAddressBarIsEnabledThenEmitEnabled() = runTest { + whenever(duckChat.observeShowInAddressBarUserSetting()).thenReturn(flowOf(true)) + testee = DuckAiShortcutSettingsViewModel(duckChat) + + testee.viewState.test { + assertTrue(awaitItem().showInAddressBar) + } + } + + @Test + fun whenViewModelIsCreatedAndShowInAddressBarIsDisabledThenEmitDisabled() = runTest { + whenever(duckChat.observeShowInAddressBarUserSetting()).thenReturn(flowOf(false)) + testee = DuckAiShortcutSettingsViewModel(duckChat) + + testee.viewState.test { + assertFalse(awaitItem().showInAddressBar) + } + } + + @Test + fun whenAddressBarEntryPointEnabledTogglesShown() = runTest { + whenever(duckChat.isAddressBarEntryPointEnabled()).thenReturn(true) + testee = DuckAiShortcutSettingsViewModel(duckChat) + + testee.viewState.test { + val state = awaitItem() + assertTrue(state.shouldShowAddressBarToggle) + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun whenAddressBarEntryPointDisabledThenToggleHidden() = runTest { + whenever(duckChat.isAddressBarEntryPointEnabled()).thenReturn(false) + testee = DuckAiShortcutSettingsViewModel(duckChat) + + testee.viewState.test { + val state = awaitItem() + assertFalse(state.shouldShowAddressBarToggle) + } + } +} diff --git a/duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/ui/DuckChatSettingsViewModelTest.kt b/duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsViewModelTest.kt similarity index 66% rename from duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/ui/DuckChatSettingsViewModelTest.kt rename to duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsViewModelTest.kt index 52dd1761b9c9..96434520a6b0 100644 --- a/duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/ui/DuckChatSettingsViewModelTest.kt +++ b/duckchat/duckchat-impl/src/test/kotlin/com/duckduckgo/duckchat/impl/ui/settings/DuckChatSettingsViewModelTest.kt @@ -14,16 +14,17 @@ * limitations under the License. */ -package com.duckduckgo.duckchat.impl.ui +package com.duckduckgo.duckchat.impl.ui.settings import app.cash.turbine.test import com.duckduckgo.app.statistics.pixels.Pixel import com.duckduckgo.common.test.CoroutineTestRule -import com.duckduckgo.common.ui.experiments.visual.store.ExperimentalThemingDataStore import com.duckduckgo.duckchat.impl.DuckChatInternal import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName -import com.duckduckgo.duckchat.impl.ui.DuckChatSettingsViewModel.Command.OpenLink -import com.duckduckgo.duckchat.impl.ui.DuckChatSettingsViewModel.Command.OpenLinkInNewTab +import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.Command.LaunchFeedback +import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.Command.OpenLink +import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.Command.OpenLinkInNewTab +import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.Command.OpenShortcutSettings import com.duckduckgo.subscriptions.api.SubscriptionRebrandingFeatureToggle import junit.framework.TestCase.assertEquals import kotlinx.coroutines.flow.flowOf @@ -47,7 +48,6 @@ class DuckChatSettingsViewModelTest { private val duckChat: DuckChatInternal = mock() private val mockPixel: Pixel = mock() - private val mockExperimentalThemingDataStore: ExperimentalThemingDataStore = mock() private val mockRebrandingFeatureToggle: SubscriptionRebrandingFeatureToggle = mock() @Before @@ -56,7 +56,7 @@ class DuckChatSettingsViewModelTest { whenever(duckChat.observeShowInBrowserMenuUserSetting()).thenReturn(flowOf(false)) whenever(duckChat.observeShowInAddressBarUserSetting()).thenReturn(flowOf(false)) whenever(duckChat.observeInputScreenUserSettingEnabled()).thenReturn(flowOf(false)) - testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockExperimentalThemingDataStore, mockRebrandingFeatureToggle) + testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockRebrandingFeatureToggle) } @Test @@ -96,89 +96,21 @@ class DuckChatSettingsViewModelTest { } @Test - fun `when onDuckAiInputScreen enabled then set user setting`() = runTest { - testee.onDuckAiInputScreenToggled(true) + fun `when onDuckAiInputScreenWithAiSelected selected then set user setting`() = runTest { + testee.onDuckAiInputScreenWithAiSelected() verify(duckChat).setInputScreenUserSetting(true) } @Test - fun `when onDuckAiInputScreen disabled then set user setting`() = runTest { - testee.onDuckAiInputScreenToggled(false) + fun `when onDuckAiInputScreenWithoutAiSelected selected then set user setting`() = runTest { + testee.onDuckAiInputScreenWithoutAiSelected() verify(duckChat).setInputScreenUserSetting(false) } - @Test - fun whenViewModelIsCreatedAndShowInBrowserIsEnabledThenEmitEnabled() = runTest { - whenever(duckChat.observeShowInBrowserMenuUserSetting()).thenReturn(flowOf(true)) - testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockExperimentalThemingDataStore, mockRebrandingFeatureToggle) - - testee.viewState.test { - assertTrue(awaitItem().showInBrowserMenu) - } - } - - @Test - fun whenViewModelIsCreatedAndShowInBrowserIsDisabledThenEmitDisabled() = runTest { - whenever(duckChat.observeShowInBrowserMenuUserSetting()).thenReturn(flowOf(false)) - testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockExperimentalThemingDataStore, mockRebrandingFeatureToggle) - - testee.viewState.test { - assertFalse(awaitItem().showInBrowserMenu) - } - } - - @Test - fun whenViewModelIsCreatedAndShowInAddressBarIsEnabledThenEmitEnabled() = runTest { - whenever(duckChat.observeShowInAddressBarUserSetting()).thenReturn(flowOf(true)) - testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockExperimentalThemingDataStore, mockRebrandingFeatureToggle) - - testee.viewState.test { - assertTrue(awaitItem().showInAddressBar) - } - } - - @Test - fun whenViewModelIsCreatedAndShowInAddressBarIsDisabledThenEmitDisabled() = runTest { - whenever(duckChat.observeShowInAddressBarUserSetting()).thenReturn(flowOf(false)) - testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockExperimentalThemingDataStore, mockRebrandingFeatureToggle) - - testee.viewState.test { - assertFalse(awaitItem().showInAddressBar) - } - } - - @Test - fun whenDuckChatEnabledAndAddressBarEntryPointEnabledThenBothSubTogglesShown() = runTest { - whenever(duckChat.observeEnableDuckChatUserSetting()).thenReturn(flowOf(true)) - whenever(duckChat.isAddressBarEntryPointEnabled()).thenReturn(true) - testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockExperimentalThemingDataStore, mockRebrandingFeatureToggle) - - testee.viewState.test { - val state = awaitItem() - assertTrue(state.shouldShowBrowserMenuToggle) - assertTrue(state.shouldShowAddressBarToggle) - cancelAndIgnoreRemainingEvents() - } - } - - @Test - fun whenDuckChatEnabledAndAddressBarEntryPointDisabledThenOnlyBrowserToggleShown() = runTest { - whenever(duckChat.observeInputScreenUserSettingEnabled()).thenReturn(flowOf(false)) - whenever(duckChat.observeEnableDuckChatUserSetting()).thenReturn(flowOf(true)) - whenever(duckChat.isAddressBarEntryPointEnabled()).thenReturn(false) - testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockExperimentalThemingDataStore, mockRebrandingFeatureToggle) - - testee.viewState.test { - val state = awaitItem() - assertTrue(state.shouldShowBrowserMenuToggle) - assertFalse(state.shouldShowAddressBarToggle) - } - } - @Test fun `input screen - user preference enabled then set correct state`() = runTest { whenever(duckChat.observeInputScreenUserSettingEnabled()).thenReturn(flowOf(true)) - testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockExperimentalThemingDataStore, mockRebrandingFeatureToggle) + testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockRebrandingFeatureToggle) testee.viewState.test { assertTrue(awaitItem().isInputScreenEnabled) @@ -188,7 +120,7 @@ class DuckChatSettingsViewModelTest { @Test fun `input screen - user preference disabled then set correct state`() = runTest { whenever(duckChat.observeInputScreenUserSettingEnabled()).thenReturn(flowOf(false)) - testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockExperimentalThemingDataStore, mockRebrandingFeatureToggle) + testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockRebrandingFeatureToggle) testee.viewState.test { assertFalse(awaitItem().isInputScreenEnabled) @@ -199,7 +131,7 @@ class DuckChatSettingsViewModelTest { fun `input screen - when duck chat enabled and flag enabled, then emit enabled`() = runTest { whenever(duckChat.observeEnableDuckChatUserSetting()).thenReturn(flowOf(true)) whenever(duckChat.isInputScreenFeatureAvailable()).thenReturn(true) - testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockExperimentalThemingDataStore, mockRebrandingFeatureToggle) + testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockRebrandingFeatureToggle) testee.viewState.test { val state = awaitItem() @@ -211,7 +143,7 @@ class DuckChatSettingsViewModelTest { fun `input screen - when flag disabled, then emit disabled`() = runTest { whenever(duckChat.observeEnableDuckChatUserSetting()).thenReturn(flowOf(true)) whenever(duckChat.isInputScreenFeatureAvailable()).thenReturn(false) - testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockExperimentalThemingDataStore, mockRebrandingFeatureToggle) + testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockRebrandingFeatureToggle) testee.viewState.test { val state = awaitItem() @@ -222,14 +154,11 @@ class DuckChatSettingsViewModelTest { @Test fun whenDuckChatDisabledThenNoSubTogglesShown() = runTest { whenever(duckChat.observeEnableDuckChatUserSetting()).thenReturn(flowOf(false)) - whenever(duckChat.isAddressBarEntryPointEnabled()).thenReturn(true) whenever(duckChat.observeInputScreenUserSettingEnabled()).thenReturn(flowOf(true)) - testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockExperimentalThemingDataStore, mockRebrandingFeatureToggle) + testee = DuckChatSettingsViewModel(duckChat, mockPixel, mockRebrandingFeatureToggle) testee.viewState.test { val state = awaitItem() - assertFalse(state.shouldShowBrowserMenuToggle) - assertFalse(state.shouldShowAddressBarToggle) assertFalse(state.shouldShowInputScreenToggle) } } @@ -285,14 +214,14 @@ class DuckChatSettingsViewModelTest { } @Test - fun `when onDuckAiInputScreenToggled true then on pixel fired`() = runTest { - testee.onDuckAiInputScreenToggled(true) + fun `when onDuckAiInputScreenWithAiSelected true then on pixel fired`() = runTest { + testee.onDuckAiInputScreenWithAiSelected() verify(mockPixel).fire(DuckChatPixelName.DUCK_CHAT_EXPERIMENTAL_ADDRESS_BAR_SETTING_ON) } @Test - fun `when onDuckAiInputScreenToggled false then off pixel fired`() = runTest { - testee.onDuckAiInputScreenToggled(false) + fun `when onDuckAiInputScreenWithoutAiSelected false then off pixel fired`() = runTest { + testee.onDuckAiInputScreenWithoutAiSelected() verify(mockPixel).fire(DuckChatPixelName.DUCK_CHAT_EXPERIMENTAL_ADDRESS_BAR_SETTING_OFF) } @@ -319,4 +248,26 @@ class DuckChatSettingsViewModelTest { testee.onShowDuckChatInAddressBarToggled(false) verify(mockPixel).fire(DuckChatPixelName.DUCK_CHAT_SEARCHBAR_SETTING_OFF) } + + @Test + fun `when Duck ai shortcuts clicked, then dispatch launch command`() = runTest { + testee.onDuckAiShortcutsClicked() + + testee.commands.test { + val command = awaitItem() + assertTrue(command is OpenShortcutSettings) + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `when input toggle share feedback clicked, then dispatch launch command`() = runTest { + testee.duckAiInputScreenShareFeedbackClicked() + + testee.commands.test { + val command = awaitItem() + assertTrue(command is LaunchFeedback) + cancelAndIgnoreRemainingEvents() + } + } }