Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show on App Launch: Setting screen #4950

5 changes: 5 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,11 @@
android:exported="false"
android:label="@string/generalSettingsActivityTitle"
android:parentActivityName="com.duckduckgo.app.settings.SettingsActivity" />
<activity
android:name="com.duckduckgo.app.generalsettings.showonapplaunch.ShowOnAppLaunchActivity"
android:exported="false"
android:label="@string/showOnAppLaunchOptionTitle"
android:parentActivityName="com.duckduckgo.app.generalsettings.GeneralSettingsActivity" />
<activity
android:name="com.duckduckgo.app.webtrackingprotection.WebTrackingProtectionActivity"
android:exported="false"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,8 @@ open class BrowserActivity : DuckDuckGoActivity() {
return
}
}

viewModel.handleShowOnAppLaunchOption()
}

private fun configureObservers() {
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/java/com/duckduckgo/app/browser/BrowserViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,16 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.duckduckgo.anvil.annotations.ContributesRemoteFeature
import com.duckduckgo.anvil.annotations.ContributesViewModel
import com.duckduckgo.app.browser.defaultbrowsing.DefaultBrowserDetector
import com.duckduckgo.app.browser.omnibar.OmnibarEntryConverter
import com.duckduckgo.app.fire.DataClearer
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.LastOpenedTab
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.NewTabPage
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.SpecificPage
import com.duckduckgo.app.generalsettings.showonapplaunch.store.ShowOnAppLaunchOptionDataStore
import com.duckduckgo.app.global.ApplicationClearDataState
import com.duckduckgo.app.global.rating.AppEnjoymentPromptEmitter
import com.duckduckgo.app.global.rating.AppEnjoymentPromptOptions
Expand Down Expand Up @@ -55,6 +60,7 @@ import com.duckduckgo.feature.toggles.api.Toggle
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import timber.log.Timber

Expand All @@ -69,6 +75,7 @@ class BrowserViewModel @Inject constructor(
private val dispatchers: DispatcherProvider,
private val pixel: Pixel,
private val skipUrlConversionOnNewTabFeature: SkipUrlConversionOnNewTabFeature,
private val showOnAppLaunchOptionDataStore: ShowOnAppLaunchOptionDataStore,
) : ViewModel(),
CoroutineScope {

Expand Down Expand Up @@ -284,6 +291,21 @@ class BrowserViewModel @Inject constructor(
fun onBookmarksActivityResult(url: String) {
command.value = Command.OpenSavedSite(url)
}

fun handleShowOnAppLaunchOption() {
viewModelScope.launch {
when (val option = showOnAppLaunchOptionDataStore.optionFlow.first()) {
LastOpenedTab -> Unit
NewTabPage -> onNewTabRequested()
is SpecificPage -> {
val liveSelectedTabUrl = tabRepository.getSelectedTab()?.url
if (liveSelectedTabUrl != option.url) {
onOpenInNewTabRequested(option.url)
}
}
}
}
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,16 @@ import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.duckduckgo.anvil.annotations.ContributeToActivityStarter
import com.duckduckgo.anvil.annotations.InjectWith
import com.duckduckgo.app.browser.R
import com.duckduckgo.app.browser.databinding.ActivityGeneralSettingsBinding
import com.duckduckgo.app.generalsettings.GeneralSettingsViewModel.Command
import com.duckduckgo.app.generalsettings.GeneralSettingsViewModel.Command.LaunchShowOnAppLaunchScreen
import com.duckduckgo.app.generalsettings.showonapplaunch.ShowOnAppLaunchScreenNoParams
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.LastOpenedTab
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.NewTabPage
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.SpecificPage
import com.duckduckgo.app.global.view.fadeTransitionConfig
import com.duckduckgo.common.ui.DuckDuckGoActivity
import com.duckduckgo.common.ui.viewbinding.viewBinding
import com.duckduckgo.di.scopes.ActivityScope
Expand Down Expand Up @@ -102,7 +109,7 @@ class GeneralSettingsActivity : DuckDuckGoActivity() {
binding.voiceSearchToggle.isVisible = true
binding.voiceSearchToggle.quietlySetIsChecked(viewState.voiceSearchEnabled, voiceSearchChangeListener)
}
binding.showOnAppLaunchButton.setSecondaryText(viewState.showOnAppLaunchSelectedOptionText)
setShowOnAppLaunchOptionSecondaryText(viewState.showOnAppLaunchSelectedOption)
}
}.launchIn(lifecycleScope)

Expand All @@ -112,10 +119,19 @@ class GeneralSettingsActivity : DuckDuckGoActivity() {
.launchIn(lifecycleScope)
}

private fun setShowOnAppLaunchOptionSecondaryText(showOnAppLaunchOption: ShowOnAppLaunchOption) {
val optionString = when (showOnAppLaunchOption) {
is LastOpenedTab -> getString(R.string.showOnAppLaunchOptionLastOpenedTab)
is NewTabPage -> getString(R.string.showOnAppLaunchOptionNewTabPage)
is SpecificPage -> showOnAppLaunchOption.url
}
binding.showOnAppLaunchButton.setSecondaryText(optionString)
}

private fun processCommand(command: Command) {
when (command) {
LaunchShowOnAppLaunchScreen -> {
// TODO launch show on app launch screen
globalActivityStarter.start(this, ShowOnAppLaunchScreenNoParams, fadeTransitionConfig())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ package com.duckduckgo.app.generalsettings
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.duckduckgo.anvil.annotations.ContributesViewModel
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption
import com.duckduckgo.app.generalsettings.showonapplaunch.store.ShowOnAppLaunchOptionDataStore
import com.duckduckgo.app.pixels.AppPixelName
import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_GENERAL_SETTINGS_TOGGLED_OFF
import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_GENERAL_SETTINGS_TOGGLED_ON
import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_RECENT_SITES_GENERAL_SETTINGS_TOGGLED_OFF
Expand All @@ -37,7 +40,11 @@ import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber

Expand All @@ -49,6 +56,7 @@ class GeneralSettingsViewModel @Inject constructor(
private val voiceSearchAvailability: VoiceSearchAvailability,
private val voiceSearchRepository: VoiceSearchRepository,
private val dispatcherProvider: DispatcherProvider,
private val showOnAppLaunchOptionDataStore: ShowOnAppLaunchOptionDataStore,
) : ViewModel() {

data class ViewState(
Expand All @@ -57,7 +65,7 @@ class GeneralSettingsViewModel @Inject constructor(
val storeHistoryEnabled: Boolean,
val showVoiceSearch: Boolean,
val voiceSearchEnabled: Boolean,
val showOnAppLaunchSelectedOptionText: String,
val showOnAppLaunchSelectedOption: ShowOnAppLaunchOption,
)

sealed class Command {
Expand All @@ -82,10 +90,11 @@ class GeneralSettingsViewModel @Inject constructor(
storeHistoryEnabled = history.isHistoryFeatureAvailable(),
showVoiceSearch = voiceSearchAvailability.isVoiceSearchSupported,
voiceSearchEnabled = voiceSearchAvailability.isVoiceSearchAvailable,
// TODO get the actual value from prefs
showOnAppLaunchSelectedOptionText = "Last Opened Tab",
showOnAppLaunchSelectedOption = showOnAppLaunchOptionDataStore.optionFlow.first(),
)
}

observeShowOnAppLaunchOption()
}

fun onAutocompleteSettingChanged(enabled: Boolean) {
Expand Down Expand Up @@ -135,6 +144,14 @@ class GeneralSettingsViewModel @Inject constructor(

fun onShowOnAppLaunchButtonClick() {
sendCommand(Command.LaunchShowOnAppLaunchScreen)
pixel.fire(AppPixelName.SETTINGS_GENERAL_APP_LAUNCH_PRESSED)
}

private fun observeShowOnAppLaunchOption() {
showOnAppLaunchOptionDataStore.optionFlow
.onEach { showOnAppLaunchOption ->
_viewState.update { it!!.copy(showOnAppLaunchSelectedOption = showOnAppLaunchOption) }
}.launchIn(viewModelScope)
}

private fun sendCommand(newCommand: Command) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright (c) 2024 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.app.generalsettings.showonapplaunch

import android.os.Bundle
import android.view.MenuItem
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.duckduckgo.anvil.annotations.ContributeToActivityStarter
import com.duckduckgo.anvil.annotations.InjectWith
import com.duckduckgo.app.browser.databinding.ActivityShowOnAppLaunchSettingBinding
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.LastOpenedTab
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.NewTabPage
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption.SpecificPage
import com.duckduckgo.common.ui.DuckDuckGoActivity
import com.duckduckgo.common.ui.viewbinding.viewBinding
import com.duckduckgo.di.scopes.ActivityScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach

@InjectWith(ActivityScope::class)
@ContributeToActivityStarter(ShowOnAppLaunchScreenNoParams::class)
class ShowOnAppLaunchActivity : DuckDuckGoActivity() {

private val viewModel: ShowOnAppLaunchViewModel by bindViewModel()
private val binding: ActivityShowOnAppLaunchSettingBinding by viewBinding()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContentView(binding.root)
setupToolbar(binding.includeToolbar.toolbar)

binding.specificPageUrlInput.setSelectAllOnFocus(true)

configureUiEventHandlers()
observeViewModel()
}

override fun onPause() {
super.onPause()
viewModel.setSpecificPageUrl(binding.specificPageUrlInput.text)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
finish()
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
true
}
else -> super.onOptionsItemSelected(item)
}
}

private fun configureUiEventHandlers() {
binding.lastOpenedTabCheckListItem.setClickListener {
viewModel.onShowOnAppLaunchOptionChanged(LastOpenedTab)
}

binding.newTabCheckListItem.setClickListener {
viewModel.onShowOnAppLaunchOptionChanged(NewTabPage)
}

binding.specificPageCheckListItem.setClickListener {
viewModel.onShowOnAppLaunchOptionChanged(SpecificPage(binding.specificPageUrlInput.text))
}

binding.specificPageUrlInput.addFocusChangedListener { _, hasFocus ->
if (hasFocus) {
viewModel.onShowOnAppLaunchOptionChanged(
SpecificPage(binding.specificPageUrlInput.text),
)
}
}
}

private fun observeViewModel() {
viewModel.viewState
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.onEach { viewState ->
when (viewState.selectedOption) {
LastOpenedTab -> {
uncheckNewTabCheckListItem()
uncheckSpecificPageCheckListItem()
binding.lastOpenedTabCheckListItem.setChecked(true)
}
NewTabPage -> {
uncheckLastOpenedTabCheckListItem()
uncheckSpecificPageCheckListItem()
binding.newTabCheckListItem.setChecked(true)
}
is SpecificPage -> {
uncheckLastOpenedTabCheckListItem()
uncheckNewTabCheckListItem()
binding.specificPageCheckListItem.setChecked(true)
}
}

if (binding.specificPageUrlInput.text != viewState.specificPageUrl) {
binding.specificPageUrlInput.text = viewState.specificPageUrl
}
}
.launchIn(lifecycleScope)
}

private fun uncheckLastOpenedTabCheckListItem() {
binding.lastOpenedTabCheckListItem.setChecked(false)
}

private fun uncheckNewTabCheckListItem() {
binding.newTabCheckListItem.setChecked(false)
}

private fun uncheckSpecificPageCheckListItem() {
binding.specificPageCheckListItem.setChecked(false)
binding.specificPageUrlInput.isEditable = false
binding.specificPageUrlInput.isEditable = true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2024 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.app.generalsettings.showonapplaunch

import com.duckduckgo.navigation.api.GlobalActivityStarter.ActivityParams

/**
* Use this model to launch the Show On App Launch screen
*/
object ShowOnAppLaunchScreenNoParams : ActivityParams
Loading
Loading