Skip to content

Commit

Permalink
Show on App Launch: Feature toggle (#5127)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/1205782002757341/1208523107235076/f

Adds a feature toggle for the Show on App Launch feature that defaults
to on.

- [x] Open General Settings
- [x] Check Show on App Launch button is visible
- [x] Open FF Inventory
- [x] Disable `showOnAppLaunch`
- [x] Open General Settings
- [x] Check Show on App Launch button is gone
- [x] Open FF Inventory
- [x] Enable `showOnAppLaunch`
- [x] Open General Settings
- [x] Check Show on App Launch button is visible

N/A
  • Loading branch information
mikescamell committed Nov 6, 2024
1 parent b88274f commit 8c0a03a
Show file tree
Hide file tree
Showing 21 changed files with 1,270 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteDao
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteEntity
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteRepositoryImpl
import com.duckduckgo.app.fire.fireproofwebsite.ui.AutomaticFireproofSetting
import com.duckduckgo.app.generalsettings.showonapplaunch.ShowOnAppLaunchOptionHandler
import com.duckduckgo.app.global.db.AppDatabase
import com.duckduckgo.app.global.events.db.UserEventsStore
import com.duckduckgo.app.global.install.AppInstallStore
Expand Down Expand Up @@ -412,6 +413,8 @@ class BrowserTabViewModelTest {

private var loadingBarExperimentManager: LoadingBarExperimentManager = mock()

private val mockShowOnAppLaunchHandler: ShowOnAppLaunchOptionHandler = mock()

private lateinit var remoteMessagingModel: RemoteMessagingModel

private val lazyFaviconManager = Lazy { mockFaviconManager }
Expand Down Expand Up @@ -669,6 +672,7 @@ class BrowserTabViewModelTest {
changeOmnibarPositionFeature = changeOmnibarPositionFeature,
highlightsOnboardingExperimentManager = mockHighlightsOnboardingExperimentManager,
privacyProtectionTogglePlugin = protectionTogglePluginPoint,
showOnAppLaunchOptionHandler = mockShowOnAppLaunchHandler,
)

testee.loadData("abc", null, false, false)
Expand Down Expand Up @@ -6127,6 +6131,13 @@ class BrowserTabViewModelTest {
}
}

@Test
fun whenNavigationStateChangedCalledThenHandleResolvedUrlIsChecked() = runTest {
testee.navigationStateChanged(buildWebNavigation("https://example.com"))

verify(mockShowOnAppLaunchHandler).handleResolvedUrlStorage(eq("https://example.com"), any(), any())
}

private fun aCredential(): LoginCredentials {
return LoginCredentials(domain = null, username = null, password = null)
}
Expand Down
30 changes: 30 additions & 0 deletions app/src/androidTest/java/com/duckduckgo/app/tabs/db/TabsDaoTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.duckduckgo.app.global.db.AppDatabase
import com.duckduckgo.app.tabs.model.TabEntity
import com.duckduckgo.app.tabs.model.TabSelectionEntity
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
Expand Down Expand Up @@ -337,4 +338,33 @@ class TabsDaoTest {

assertEquals(tab.copy(deletable = false), testee.tab(tab.tabId))
}

@Test
fun whenSelectTabByUrlAndTabExistsThenTabIdReturned() = runTest {
val tab = TabEntity(
tabId = "TAB_ID",
url = "https://www.duckduckgo.com/",
position = 0,
deletable = true,
)

testee.insertTab(tab)
val tabId = testee.selectTabByUrl("https://www.duckduckgo.com/")

assertEquals(tabId, tab.tabId)
}

@Test
fun whenSelectTabByUrlAndTabDoesNotExistThenNullReturned() = runTest {
val tab = TabEntity(
tabId = "TAB_ID",
url = "https://www.duckduckgo.com/",
position = 0,
)

testee.insertTab(tab)
val tabId = testee.selectTabByUrl("https://www.quackquackno.com/")

assertNull(tabId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,9 @@ open class BrowserActivity : DuckDuckGoActivity() {
Timber.i("shared text empty, opening last tab")
}

viewModel.handleShowOnAppLaunchOption()
if (!intent.getBooleanExtra(LAUNCH_FROM_CLEAR_DATA_ACTION, false)) {
viewModel.handleShowOnAppLaunchOption()
}
}

private fun configureObservers() {
Expand Down Expand Up @@ -585,6 +587,7 @@ open class BrowserActivity : DuckDuckGoActivity() {
isExternal: Boolean = false,
interstitialScreen: Boolean = false,
openExistingTabId: String? = null,
isLaunchFromClearDataAction: Boolean = false,
): Intent {
val intent = Intent(context, BrowserActivity::class.java)
intent.putExtra(EXTRA_TEXT, queryExtra)
Expand All @@ -595,6 +598,7 @@ open class BrowserActivity : DuckDuckGoActivity() {
intent.putExtra(LAUNCH_FROM_EXTERNAL_EXTRA, isExternal)
intent.putExtra(LAUNCH_FROM_INTERSTITIAL_EXTRA, interstitialScreen)
intent.putExtra(OPEN_EXISTING_TAB_ID_EXTRA, openExistingTabId)
intent.putExtra(LAUNCH_FROM_CLEAR_DATA_ACTION, isLaunchFromClearDataAction)
return intent
}

Expand All @@ -610,6 +614,7 @@ open class BrowserActivity : DuckDuckGoActivity() {
const val OPEN_EXISTING_TAB_ID_EXTRA = "OPEN_EXISTING_TAB_ID_EXTRA"

private const val LAUNCH_FROM_EXTERNAL_EXTRA = "LAUNCH_FROM_EXTERNAL_EXTRA"
private const val LAUNCH_FROM_CLEAR_DATA_ACTION = "LAUNCH_FROM_CLEAR_DATA_ACTION"

private const val MAX_ACTIVE_TABS = 40
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteEntity
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteRepository
import com.duckduckgo.app.fire.fireproofwebsite.ui.AutomaticFireproofSetting.ALWAYS
import com.duckduckgo.app.fire.fireproofwebsite.ui.AutomaticFireproofSetting.ASK_EVERY_TIME
import com.duckduckgo.app.generalsettings.showonapplaunch.ShowOnAppLaunchOptionHandler
import com.duckduckgo.app.global.events.db.UserEventKey
import com.duckduckgo.app.global.events.db.UserEventsStore
import com.duckduckgo.app.global.model.PrivacyShield
Expand Down Expand Up @@ -432,6 +433,7 @@ class BrowserTabViewModel @Inject constructor(
private val changeOmnibarPositionFeature: ChangeOmnibarPositionFeature,
private val highlightsOnboardingExperimentManager: HighlightsOnboardingExperimentManager,
private val privacyProtectionTogglePlugin: PluginPoint<PrivacyProtectionTogglePlugin>,
private val showOnAppLaunchOptionHandler: ShowOnAppLaunchOptionHandler,
) : WebViewClientListener,
EditSavedSiteListener,
DeleteBookmarkListener,
Expand Down Expand Up @@ -1329,6 +1331,15 @@ class BrowserTabViewModel @Inject constructor(

override fun navigationStateChanged(newWebNavigationState: WebNavigationState) {
val stateChange = newWebNavigationState.compare(webNavigationState)

viewModelScope.launch {
showOnAppLaunchOptionHandler.handleResolvedUrlStorage(
currentUrl = newWebNavigationState.currentUrl,
isRootOfTab = !newWebNavigationState.canGoBack,
tabId = tabId,
)
}

webNavigationState = newWebNavigationState

if (!currentBrowserViewState().browserShowing) return
Expand Down
23 changes: 7 additions & 16 deletions app/src/main/java/com/duckduckgo/app/browser/BrowserViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@ 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.generalsettings.showonapplaunch.ShowOnAppLaunchFeature
import com.duckduckgo.app.generalsettings.showonapplaunch.ShowOnAppLaunchOptionHandler
import com.duckduckgo.app.global.ApplicationClearDataState
import com.duckduckgo.app.global.rating.AppEnjoymentPromptEmitter
import com.duckduckgo.app.global.rating.AppEnjoymentPromptOptions
Expand Down Expand Up @@ -60,7 +58,6 @@ 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 @@ -75,7 +72,8 @@ class BrowserViewModel @Inject constructor(
private val dispatchers: DispatcherProvider,
private val pixel: Pixel,
private val skipUrlConversionOnNewTabFeature: SkipUrlConversionOnNewTabFeature,
private val showOnAppLaunchOptionDataStore: ShowOnAppLaunchOptionDataStore,
private val showOnAppLaunchFeature: ShowOnAppLaunchFeature,
private val showOnAppLaunchOptionHandler: ShowOnAppLaunchOptionHandler,
) : ViewModel(),
CoroutineScope {

Expand Down Expand Up @@ -299,16 +297,9 @@ class BrowserViewModel @Inject constructor(
}

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)
}
}
if (showOnAppLaunchFeature.self().isEnabled()) {
viewModelScope.launch {
showOnAppLaunchOptionHandler.handleAppLaunchOption()
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/duckduckgo/app/fire/FireActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class FireActivity : AppCompatActivity() {
context: Context,
notifyDataCleared: Boolean = false,
): Intent {
val intent = BrowserActivity.intent(context, notifyDataCleared = notifyDataCleared)
val intent = BrowserActivity.intent(context, notifyDataCleared = notifyDataCleared, isLaunchFromClearDataAction = true)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
return intent
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ class GeneralSettingsActivity : DuckDuckGoActivity() {
binding.voiceSearchToggle.isVisible = true
binding.voiceSearchToggle.quietlySetIsChecked(viewState.voiceSearchEnabled, voiceSearchChangeListener)
}

binding.showOnAppLaunchButton.isVisible = it.isShowOnAppLaunchOptionVisible
setShowOnAppLaunchOptionSecondaryText(viewState.showOnAppLaunchSelectedOption)
}
}.launchIn(lifecycleScope)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ 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.ShowOnAppLaunchFeature
import com.duckduckgo.app.generalsettings.showonapplaunch.model.ShowOnAppLaunchOption
import com.duckduckgo.app.generalsettings.showonapplaunch.store.ShowOnAppLaunchOptionDataStore
import com.duckduckgo.app.pixels.AppPixelName
Expand Down Expand Up @@ -56,6 +57,7 @@ class GeneralSettingsViewModel @Inject constructor(
private val voiceSearchAvailability: VoiceSearchAvailability,
private val voiceSearchRepository: VoiceSearchRepository,
private val dispatcherProvider: DispatcherProvider,
private val showOnAppLaunchFeature: ShowOnAppLaunchFeature,
private val showOnAppLaunchOptionDataStore: ShowOnAppLaunchOptionDataStore,
) : ViewModel() {

Expand All @@ -65,6 +67,7 @@ class GeneralSettingsViewModel @Inject constructor(
val storeHistoryEnabled: Boolean,
val showVoiceSearch: Boolean,
val voiceSearchEnabled: Boolean,
val isShowOnAppLaunchOptionVisible: Boolean,
val showOnAppLaunchSelectedOption: ShowOnAppLaunchOption,
)

Expand All @@ -90,6 +93,7 @@ class GeneralSettingsViewModel @Inject constructor(
storeHistoryEnabled = history.isHistoryFeatureAvailable(),
showVoiceSearch = voiceSearchAvailability.isVoiceSearchSupported,
voiceSearchEnabled = voiceSearchAvailability.isVoiceSearchAvailable,
isShowOnAppLaunchOptionVisible = showOnAppLaunchFeature.self().isEnabled(),
showOnAppLaunchSelectedOption = showOnAppLaunchOptionDataStore.optionFlow.first(),
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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.anvil.annotations.ContributesRemoteFeature
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.feature.toggles.api.Toggle

@ContributesRemoteFeature(
scope = AppScope::class,
featureName = "showOnAppLaunch",
)
interface ShowOnAppLaunchFeature {

@Toggle.DefaultValue(true)
fun self(): Toggle
}
Loading

0 comments on commit 8c0a03a

Please sign in to comment.