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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ import com.duckduckgo.navigation.api.GlobalActivityStarter
* Use this model to launch the DuckChat Settings screen
*/
object DuckChatSettingsNoParams : GlobalActivityStarter.ActivityParams

/**
* Use this model to launch the DuckChat Settings screen and show native settings only
*/
data object DuckChatNativeSettingsNoParams : GlobalActivityStarter.ActivityParams
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.duckduckgo.anvil.annotations.ContributeToActivityStarter
Expand All @@ -41,13 +43,16 @@ import com.duckduckgo.common.ui.store.AppTheme
import com.duckduckgo.common.ui.view.addClickableSpan
import com.duckduckgo.common.ui.viewbinding.viewBinding
import com.duckduckgo.di.scopes.ActivityScope
import com.duckduckgo.duckchat.api.DuckChatNativeSettingsNoParams
import com.duckduckgo.duckchat.api.DuckChatSettingsNoParams
import com.duckduckgo.duckchat.impl.R
import com.duckduckgo.duckchat.impl.databinding.ActivityDuckChatSettingsBinding
import com.duckduckgo.duckchat.impl.inputscreen.ui.metrics.discovery.InputScreenDiscoveryFunnel
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName.DUCK_CHAT_SETTINGS_DISPLAYED
import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.DuckChatSettingsViewModelFactory
import com.duckduckgo.duckchat.impl.ui.settings.DuckChatSettingsViewModel.ViewState
import com.duckduckgo.navigation.api.GlobalActivityStarter
import com.duckduckgo.navigation.api.getActivityParams
import com.duckduckgo.settings.api.SettingsPageFeature
import com.duckduckgo.settings.api.SettingsWebViewScreenWithParams
import kotlinx.coroutines.flow.launchIn
Expand All @@ -57,10 +62,19 @@ import com.duckduckgo.mobile.android.R as CommonR

@InjectWith(ActivityScope::class)
@ContributeToActivityStarter(DuckChatSettingsNoParams::class, screenName = "duckai.settings")
@ContributeToActivityStarter(DuckChatNativeSettingsNoParams::class, screenName = "duckai.settings")
class DuckChatSettingsActivity : DuckDuckGoActivity() {
private val viewModel: DuckChatSettingsViewModel by bindViewModel()

@Inject
lateinit var duckChatSettingsViewModelFactory: DuckChatSettingsViewModelFactory

private val binding: ActivityDuckChatSettingsBinding by viewBinding()

private val viewModel by lazy {
val activityParams = intent.getActivityParams(GlobalActivityStarter.ActivityParams::class.java)!!
duckChatSettingsViewModel(activityParams)
}

private val userEnabledDuckChatToggleListener =
CompoundButton.OnCheckedChangeListener { _, isChecked ->
viewModel.onDuckChatUserEnabledToggled(isChecked)
Expand Down Expand Up @@ -167,22 +181,7 @@ class DuckChatSettingsActivity : DuckDuckGoActivity() {
viewModel.onDuckAiShortcutsClicked()
}

binding.showDuckChatSearchSettingsLink.setOnClickListener {
viewModel.duckChatSearchAISettingsClicked()
}

if (viewState.isHideGeneratedImagesOptionVisible) {
binding.searchSettingsSectionHeader.isVisible = true
binding.duckAiHideAiGeneratedImagesLink.apply {
isVisible = true
setOnClickListener {
viewModel.onDuckAiHideAiGeneratedImagesClicked()
}
}
} else {
binding.searchSettingsSectionHeader.isGone = true
binding.duckAiHideAiGeneratedImagesLink.isGone = true
}
renderSearchSettingsSection(viewState)

binding.duckAiInputScreenWithoutAiContainer.setOnClickListener {
viewModel.onDuckAiInputScreenWithoutAiSelected()
Expand All @@ -192,6 +191,38 @@ class DuckChatSettingsActivity : DuckDuckGoActivity() {
}
}

private fun renderSearchSettingsSection(viewState: ViewState) {
with(binding) {
if (viewState.isSearchSectionVisible) {
with(showDuckChatSearchSettingsLink) {
isVisible = true
setOnClickListener {
viewModel.duckChatSearchAISettingsClicked()
}
}

if (viewState.isHideGeneratedImagesOptionVisible) {
searchSettingsSectionHeader.isVisible = true

with(duckAiHideAiGeneratedImagesLink) {
isVisible = true
setOnClickListener {
viewModel.onDuckAiHideAiGeneratedImagesClicked()
}
}
} else {
searchSettingsSectionHeader.isGone = true
duckAiHideAiGeneratedImagesLink.isGone = true
}
} else {
divider2.isGone = true
searchSettingsSectionHeader.isGone = true
showDuckChatSearchSettingsLink.isGone = true
duckAiHideAiGeneratedImagesLink.isGone = true
}
}
}

private fun processCommand(command: DuckChatSettingsViewModel.Command) {
when (command) {
is DuckChatSettingsViewModel.Command.OpenLink -> {
Expand Down Expand Up @@ -283,4 +314,13 @@ class DuckChatSettingsActivity : DuckDuckGoActivity() {
val intent = Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE)
sendBroadcast(intent)
}

private fun duckChatSettingsViewModel(activityParams: GlobalActivityStarter.ActivityParams): DuckChatSettingsViewModel = ViewModelProvider.create(
store = viewModelStore,
factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>) = duckChatSettingsViewModelFactory.create(activityParams) as T
},
extras = this.defaultViewModelCreationExtras,
)[DuckChatSettingsViewModel::class.java]
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,22 @@ package com.duckduckgo.duckchat.impl.ui.settings
import androidx.annotation.StringRes
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.utils.DispatcherProvider
import com.duckduckgo.di.scopes.ActivityScope
import com.duckduckgo.duckchat.api.DuckChatNativeSettingsNoParams
import com.duckduckgo.duckchat.api.DuckChatSettingsNoParams
import com.duckduckgo.duckchat.impl.DuckChatInternal
import com.duckduckgo.duckchat.impl.R
import com.duckduckgo.duckchat.impl.inputscreen.ui.metrics.discovery.InputScreenDiscoveryFunnel
import com.duckduckgo.duckchat.impl.pixel.DuckChatPixelName
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.navigation.api.GlobalActivityStarter
import com.duckduckgo.settings.api.SettingsPageFeature
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.channels.BufferOverflow.DROP_OLDEST
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.SharingStarted
Expand All @@ -40,10 +44,9 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import javax.inject.Inject

@ContributesViewModel(ActivityScope::class)
class DuckChatSettingsViewModel @Inject constructor(
class DuckChatSettingsViewModel @AssistedInject constructor(
@Assisted duckChatActivityParams: GlobalActivityStarter.ActivityParams,
private val duckChat: DuckChatInternal,
private val pixel: Pixel,
private val inputScreenDiscoveryFunnel: InputScreenDiscoveryFunnel,
Expand All @@ -58,6 +61,7 @@ class DuckChatSettingsViewModel @Inject constructor(
val isInputScreenEnabled: Boolean = false,
val shouldShowShortcuts: Boolean = false,
val shouldShowInputScreenToggle: Boolean = false,
val isSearchSectionVisible: Boolean = true,
val isHideGeneratedImagesOptionVisible: Boolean = false,
)

Expand All @@ -72,6 +76,7 @@ class DuckChatSettingsViewModel @Inject constructor(
isInputScreenEnabled = isInputScreenEnabled,
shouldShowShortcuts = isDuckChatUserEnabled,
shouldShowInputScreenToggle = isDuckChatUserEnabled && duckChat.isInputScreenFeatureAvailable(),
isSearchSectionVisible = isSearchSectionVisible(duckChatActivityParams),
isHideGeneratedImagesOptionVisible = isHideAiGeneratedImagesOptionVisible,
)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), ViewState())
Expand Down Expand Up @@ -190,6 +195,17 @@ class DuckChatSettingsViewModel @Inject constructor(
}
}

private fun isSearchSectionVisible(duckChatActivityParams: GlobalActivityStarter.ActivityParams): Boolean = when (duckChatActivityParams) {
is DuckChatSettingsNoParams -> true
is DuckChatNativeSettingsNoParams -> false
else -> throw IllegalArgumentException("Unknown params type: $duckChatActivityParams")
}

@AssistedFactory
interface DuckChatSettingsViewModelFactory {
fun create(duckChatActivityParams: GlobalActivityStarter.ActivityParams): DuckChatSettingsViewModel
}

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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ 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.duckchat.api.DuckChatNativeSettingsNoParams
import com.duckduckgo.duckchat.api.DuckChatSettingsNoParams
import com.duckduckgo.duckchat.impl.DuckChatInternal
import com.duckduckgo.duckchat.impl.R
import com.duckduckgo.duckchat.impl.inputscreen.ui.metrics.discovery.InputScreenDiscoveryFunnel
Expand Down Expand Up @@ -64,6 +66,7 @@ class DuckChatSettingsViewModelTest {
whenever(duckChat.observeShowInAddressBarUserSetting()).thenReturn(flowOf(false))
whenever(duckChat.observeInputScreenUserSettingEnabled()).thenReturn(flowOf(false))
testee = DuckChatSettingsViewModel(
duckChatActivityParams = DuckChatSettingsNoParams,
duckChat = duckChat,
pixel = mockPixel,
inputScreenDiscoveryFunnel = mockInputScreenDiscoveryFunnel,
Expand Down Expand Up @@ -147,6 +150,7 @@ class DuckChatSettingsViewModelTest {
runTest {
whenever(duckChat.observeInputScreenUserSettingEnabled()).thenReturn(flowOf(true))
testee = DuckChatSettingsViewModel(
duckChatActivityParams = DuckChatSettingsNoParams,
duckChat = duckChat,
pixel = mockPixel,
inputScreenDiscoveryFunnel = mockInputScreenDiscoveryFunnel,
Expand All @@ -164,6 +168,7 @@ class DuckChatSettingsViewModelTest {
runTest {
whenever(duckChat.observeInputScreenUserSettingEnabled()).thenReturn(flowOf(false))
testee = DuckChatSettingsViewModel(
duckChatActivityParams = DuckChatSettingsNoParams,
duckChat = duckChat,
pixel = mockPixel,
inputScreenDiscoveryFunnel = mockInputScreenDiscoveryFunnel,
Expand All @@ -182,6 +187,7 @@ class DuckChatSettingsViewModelTest {
whenever(duckChat.observeEnableDuckChatUserSetting()).thenReturn(flowOf(true))
whenever(duckChat.isInputScreenFeatureAvailable()).thenReturn(true)
testee = DuckChatSettingsViewModel(
duckChatActivityParams = DuckChatSettingsNoParams,
duckChat = duckChat,
pixel = mockPixel,
inputScreenDiscoveryFunnel = mockInputScreenDiscoveryFunnel,
Expand All @@ -201,6 +207,7 @@ class DuckChatSettingsViewModelTest {
whenever(duckChat.observeEnableDuckChatUserSetting()).thenReturn(flowOf(true))
whenever(duckChat.isInputScreenFeatureAvailable()).thenReturn(false)
testee = DuckChatSettingsViewModel(
duckChatActivityParams = DuckChatSettingsNoParams,
duckChat = duckChat,
pixel = mockPixel,
inputScreenDiscoveryFunnel = mockInputScreenDiscoveryFunnel,
Expand All @@ -220,6 +227,7 @@ class DuckChatSettingsViewModelTest {
whenever(duckChat.observeEnableDuckChatUserSetting()).thenReturn(flowOf(false))
whenever(duckChat.observeInputScreenUserSettingEnabled()).thenReturn(flowOf(true))
testee = DuckChatSettingsViewModel(
duckChatActivityParams = DuckChatSettingsNoParams,
duckChat = duckChat,
pixel = mockPixel,
inputScreenDiscoveryFunnel = mockInputScreenDiscoveryFunnel,
Expand Down Expand Up @@ -381,6 +389,7 @@ class DuckChatSettingsViewModelTest {
@Suppress("DenyListedApi")
settingsPageFeature.hideAiGeneratedImagesOption().setRawStoredState(State(enable = true))
testee = DuckChatSettingsViewModel(
duckChatActivityParams = DuckChatSettingsNoParams,
duckChat = duckChat,
pixel = mockPixel,
inputScreenDiscoveryFunnel = mockInputScreenDiscoveryFunnel,
Expand All @@ -400,6 +409,7 @@ class DuckChatSettingsViewModelTest {
@Suppress("DenyListedApi")
settingsPageFeature.hideAiGeneratedImagesOption().setRawStoredState(State(enable = false))
testee = DuckChatSettingsViewModel(
duckChatActivityParams = DuckChatSettingsNoParams,
duckChat = duckChat,
pixel = mockPixel,
inputScreenDiscoveryFunnel = mockInputScreenDiscoveryFunnel,
Expand All @@ -412,4 +422,40 @@ class DuckChatSettingsViewModelTest {
assertFalse(state.isHideGeneratedImagesOptionVisible)
}
}

@Test
fun `when DuckChatSettingsNoParams passed then viewState shows search section visible`() =
runTest {
testee = DuckChatSettingsViewModel(
duckChatActivityParams = DuckChatSettingsNoParams,
duckChat = duckChat,
pixel = mockPixel,
inputScreenDiscoveryFunnel = mockInputScreenDiscoveryFunnel,
settingsPageFeature = settingsPageFeature,
dispatcherProvider = coroutineRule.testDispatcherProvider,
)

testee.viewState.test {
val state = awaitItem()
assertTrue(state.isSearchSectionVisible)
}
}

@Test
fun `when DuckChatNativeSettingsNoParams passed then viewState shows search section hidden`() =
runTest {
testee = DuckChatSettingsViewModel(
duckChatActivityParams = DuckChatNativeSettingsNoParams,
duckChat = duckChat,
pixel = mockPixel,
inputScreenDiscoveryFunnel = mockInputScreenDiscoveryFunnel,
settingsPageFeature = settingsPageFeature,
dispatcherProvider = coroutineRule.testDispatcherProvider,
)

testee.viewState.test {
val state = awaitItem()
assertFalse(state.isSearchSectionVisible)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import com.duckduckgo.common.utils.AppUrl
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.contentscopescripts.api.ContentScopeJsMessageHandlersPlugin
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.duckchat.api.DuckChatSettingsNoParams
import com.duckduckgo.duckchat.api.DuckChatNativeSettingsNoParams
import com.duckduckgo.js.messaging.api.JsMessage
import com.duckduckgo.js.messaging.api.JsMessageCallback
import com.duckduckgo.js.messaging.api.JsMessageHandler
Expand Down Expand Up @@ -63,7 +63,7 @@ class OpenNativeSettingsHandler @Inject constructor(

when (val screenParam = params.optString("screen", "")) {
AI_FEATURES_SCREEN_NAME -> {
val intent = globalActivityStarter.startIntent(context, DuckChatSettingsNoParams)
val intent = globalActivityStarter.startIntent(context, DuckChatNativeSettingsNoParams)
intent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(intent)
}
Expand Down
Loading