Skip to content
Open
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 @@ -4256,6 +4256,45 @@ class BrowserTabViewModelTest {
assertFalse(loadingViewState().trackersAnimationEnabled)
}

@Test
fun whenUserPreferenceDisabledThenTrackersAnimationDisabled() {
whenever(mockSettingsDataStore.showTrackersCountInAddressBar).thenReturn(false)
loadUrl("https://example.com")
assertFalse(loadingViewState().trackersAnimationEnabled)
}

@Test
fun whenUserPreferenceEnabledAndPrivacyProtectionActiveThenTrackersAnimationEnabled() {
whenever(mockSettingsDataStore.showTrackersCountInAddressBar).thenReturn(true)
whenever(mockContentBlocking.isAnException("example.com")).thenReturn(false)
loadUrl("https://example.com")
assertTrue(loadingViewState().trackersAnimationEnabled)
}

@Test
fun whenUserPreferenceDisabledEvenWithPrivacyProtectionActiveThenTrackersAnimationDisabled() {
whenever(mockSettingsDataStore.showTrackersCountInAddressBar).thenReturn(false)
whenever(mockContentBlocking.isAnException("example.com")).thenReturn(false)
loadUrl("https://example.com")
assertFalse(loadingViewState().trackersAnimationEnabled)
}

@Test
fun whenUserPreferenceEnabledButPrivacyProtectionDisabledThenTrackersAnimationDisabled() {
whenever(mockSettingsDataStore.showTrackersCountInAddressBar).thenReturn(true)
whenever(mockContentBlocking.isAnException("example.com")).thenReturn(true)
loadUrl("https://example.com")
assertFalse(loadingViewState().trackersAnimationEnabled)
}

@Test
fun whenUserPreferenceDisabledAndPrivacyProtectionDisabledThenTrackersAnimationDisabled() {
whenever(mockSettingsDataStore.showTrackersCountInAddressBar).thenReturn(false)
whenever(mockContentBlocking.isAnException("example.com")).thenReturn(true)
loadUrl("https://example.com")
assertFalse(loadingViewState().trackersAnimationEnabled)
}

@Test
fun whenEditBookmarkRequestedThenRepositoryIsNotUpdated() =
runTest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ class AppearanceActivity : DuckDuckGoActivity() {
viewModel.onShowTrackersCountInTabSwitcherChanged(isChecked)
}

private val showTrackersCountInAddressBar =
CompoundButton.OnCheckedChangeListener { _, isChecked ->
viewModel.onShowTrackersCountInAddressBarChanged(isChecked)
}

private val changeIconFlow =
registerForActivityResult(ChangeIconContract()) { resultOk ->
if (resultOk) {
Expand Down Expand Up @@ -173,6 +178,12 @@ class AppearanceActivity : DuckDuckGoActivity() {
viewState.isTrackersCountInTabSwitcherEnabled,
showTrackersCountInTabSwitcher,
)
binding.showTrackersCountInAddressBar.isVisible = viewState.shouldShowTrackersCountInAddressBar
binding.trackersCountInAddressBarDivider.isVisible = viewState.shouldShowTrackersCountInAddressBar
binding.showTrackersCountInAddressBar.quietlySetIsChecked(
viewState.isTrackersCountInAddressBarEnabled,
showTrackersCountInAddressBar,
)
configureOmnibarSettings(it)
}
}.launchIn(lifecycleScope)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.webkit.WebViewFeature
import com.duckduckgo.anvil.annotations.ContributesViewModel
import com.duckduckgo.app.browser.animations.AddressBarTrackersAnimationManager
import com.duckduckgo.app.browser.api.OmnibarRepository
import com.duckduckgo.app.browser.omnibar.OmnibarType
import com.duckduckgo.app.browser.urldisplay.UrlDisplayRepository
Expand Down Expand Up @@ -58,6 +59,7 @@ class AppearanceViewModel @Inject constructor(
private val pixel: Pixel,
private val dispatcherProvider: DispatcherProvider,
private val tabSwitcherDataStore: TabSwitcherDataStore,
private val addressBarTrackersAnimationManager: AddressBarTrackersAnimationManager,
omnibarRepository: OmnibarRepository,
) : ViewModel() {
data class ViewState(
Expand All @@ -69,6 +71,8 @@ class AppearanceViewModel @Inject constructor(
val omnibarType: OmnibarType = OmnibarType.SINGLE_TOP,
val isFullUrlEnabled: Boolean = true,
val isTrackersCountInTabSwitcherEnabled: Boolean = true,
val isTrackersCountInAddressBarEnabled: Boolean = true,
val shouldShowTrackersCountInAddressBar: Boolean = false,
val shouldShowSplitOmnibarSettings: Boolean = false,
)

Expand All @@ -95,6 +99,7 @@ class AppearanceViewModel @Inject constructor(
supportsForceDarkMode = WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING),
omnibarType = settingsDataStore.omnibarType,
shouldShowSplitOmnibarSettings = omnibarRepository.isSplitOmnibarAvailable,
isTrackersCountInAddressBarEnabled = settingsDataStore.showTrackersCountInAddressBar,
),
)

Expand All @@ -103,9 +108,11 @@ class AppearanceViewModel @Inject constructor(
urlDisplayRepository.isFullUrlEnabled,
tabSwitcherDataStore.isTrackersAnimationInfoTileHidden(),
) { currentViewState, isFullUrlEnabled, isTrackersAnimationTileHidden ->
val isFeatureEnabled = addressBarTrackersAnimationManager.isFeatureEnabled()
currentViewState.copy(
isTrackersCountInTabSwitcherEnabled = !isTrackersAnimationTileHidden,
isFullUrlEnabled = isFullUrlEnabled,
shouldShowTrackersCountInAddressBar = isFeatureEnabled,
)
}.stateIn(viewModelScope, SharingStarted.Lazily, viewState.value)

Expand Down Expand Up @@ -194,4 +201,14 @@ class AppearanceViewModel @Inject constructor(
pixel.fire(AppPixelName.SETTINGS_APPEARANCE_IS_TRACKER_COUNT_IN_TAB_SWITCHER_TOGGLED, params)
}
}

fun onShowTrackersCountInAddressBarChanged(checked: Boolean) {
viewModelScope.launch(dispatcherProvider.io()) {
settingsDataStore.showTrackersCountInAddressBar = checked
viewState.update { it.copy(isTrackersCountInAddressBarEnabled = checked) }

val params = mapOf(Pixel.PixelParameter.IS_ENABLED to checked.toString())
pixel.fire(AppPixelName.SETTINGS_APPEARANCE_IS_TRACKER_COUNT_IN_ADDRESS_BAR_TOGGLED, params)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1753,7 +1753,8 @@ class BrowserTabViewModel @Inject constructor(
withContext(dispatchers.main()) {
loadingViewState.value =
currentLoadingViewState().copy(
trackersAnimationEnabled = !(privacyProtectionDisabled || currentBrowserViewState().maliciousSiteBlocked),
trackersAnimationEnabled = !(privacyProtectionDisabled || currentBrowserViewState().maliciousSiteBlocked)
&& settingsDataStore.showTrackersCountInAddressBar,
url = site?.url ?: "",
)
}
Expand Down Expand Up @@ -2151,7 +2152,7 @@ class BrowserTabViewModel @Inject constructor(
loadingViewState.value =
currentLoadingViewState().copy(
isLoading = true,
trackersAnimationEnabled = true,
trackersAnimationEnabled = settingsDataStore.showTrackersCountInAddressBar,
/*We set the progress to 20 so the omnibar starts animating and the user knows we are loading the page.
* We don't show the browser until the page actually starts loading, to prevent previous sites from briefly
* showing in case the URL was blocked locally and therefore never started to show*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ class OmnibarLayoutViewModel @Inject constructor(
Command.StartTrackersAnimation(
entities = decoration.entities,
isCustomTab = viewState.value.viewMode is CustomTab,
isAddressBarTrackersAnimationEnabled = addressBarTrackersAnimationManager.isFeatureEnabled(),
isAddressBarTrackersAnimationEnabled = viewState.value.isAddressBarTrackersAnimationEnabled,
),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ object PixelInterceptorPixelsRequiringDataCleaning : PixelParamRemovalPlugin {
AppPixelName.SEARCH_WIDGET_ADDED.pixelName to PixelParameter.removeAtb(),
AppPixelName.SEARCH_WIDGET_DELETED.pixelName to PixelParameter.removeAtb(),
AppPixelName.SETTINGS_APPEARANCE_IS_TRACKER_COUNT_IN_TAB_SWITCHER_TOGGLED.pixelName to PixelParameter.removeAll(),
AppPixelName.SETTINGS_APPEARANCE_IS_TRACKER_COUNT_IN_ADDRESS_BAR_TOGGLED.pixelName to PixelParameter.removeAll(),
AppPixelName.TIMEOUT_WAITING_FOR_APP_REFERRER.pixelName to PixelParameter.removeAtb(),
AppPixelName.PRODUCT_TELEMETRY_SURFACE_LANDSCAPE_ORIENTATION_USED.pixelName to PixelParameter.removeAtb(),
AppPixelName.PRODUCT_TELEMETRY_SURFACE_LANDSCAPE_ORIENTATION_USED_DAILY.pixelName to PixelParameter.removeAtb(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ enum class AppPixelName(override val pixelName: String) : Pixel.PixelName {
SETTINGS_APPEARANCE_IS_FULL_URL_OPTION_TOGGLED("m_appearance_settings_is_full_url_option_toggled"),
APPEARANCE_SETTINGS_IS_FULL_URL_ENABLED_DAILY("m_appearance_settings_is_full_url_enabled_daily"),
SETTINGS_APPEARANCE_IS_TRACKER_COUNT_IN_TAB_SWITCHER_TOGGLED("m_appearance_settings_is_tracker_count_in_tab_switcher_toggled"),
SETTINGS_APPEARANCE_IS_TRACKER_COUNT_IN_ADDRESS_BAR_TOGGLED("m_appearance_settings_is_tracker_count_in_address_bar_toggled"),
SETTINGS_APP_ICON_PRESSED("ms_app_icon_setting_pressed"),
SETTINGS_ADDRESS_BAR_POSITION_PRESSED("ms_address_bar_position_setting_pressed"),
SETTINGS_ADDRESS_BAR_POSITION_SELECTED_TOP("ms_address_bar_position_setting_selected_top"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ interface SettingsDataStore {
*/
var urlPreferenceSetByUser: Boolean
var clearDuckAiData: Boolean
var showTrackersCountInAddressBar: Boolean

/**
* Check if a value has been set to the URL display preference.
Expand Down Expand Up @@ -267,6 +268,10 @@ class SettingsSharedPreferences @Inject constructor(
get() = preferences.getBoolean(KEY_CLEAR_DUCK_AI_DATA, false)
set(enabled) = preferences.edit { putBoolean(KEY_CLEAR_DUCK_AI_DATA, enabled) }

override var showTrackersCountInAddressBar: Boolean
get() = preferences.getBoolean(KEY_SHOW_TRACKERS_COUNT_IN_ADDRESS_BAR, true)
set(enabled) = preferences.edit { putBoolean(KEY_SHOW_TRACKERS_COUNT_IN_ADDRESS_BAR, enabled) }

override fun hasBackgroundTimestampRecorded(): Boolean = preferences.contains(KEY_APP_BACKGROUNDED_TIMESTAMP)

override fun clearAppBackgroundTimestamp() = preferences.edit { remove(KEY_APP_BACKGROUNDED_TIMESTAMP) }
Expand Down Expand Up @@ -345,6 +350,7 @@ class SettingsSharedPreferences @Inject constructor(
const val URL_PREFERENCE_MIGRATED = "URL_PREFERENCE_MIGRATED"
const val URL_PREFERENCE_SET_BY_USER = "URL_PREFERENCE_SET_BY_USER"
const val KEY_CLEAR_DUCK_AI_DATA = "KEY_CLEAR_DUCK_AI_DATA"
const val KEY_SHOW_TRACKERS_COUNT_IN_ADDRESS_BAR = "KEY_SHOW_TRACKERS_COUNT_IN_ADDRESS_BAR"
}

private class FireAnimationPrefsMapper {
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/res/layout/activity_appearance.xml
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,20 @@
app:primaryTextTruncated="false"
app:showSwitch="true" />

<com.duckduckgo.common.ui.view.divider.HorizontalDivider
android:id="@+id/trackersCountInAddressBarDivider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="0dp" />

<com.duckduckgo.common.ui.view.listitem.OneLineListItem
android:id="@+id/showTrackersCountInAddressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:primaryText="@string/showTrackerCountInAddressBar"
app:primaryTextTruncated="false"
app:showSwitch="true" />

<com.duckduckgo.common.ui.view.divider.HorizontalDivider
android:id="@+id/trackersAnimationSettingDivider"
android:layout_width="match_parent"
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,7 @@

<!--TabSwitcher Trackers Animation-->
<string name="showTrackerCountInTabSwitcher">Show Tracker Count in Tab Switcher</string>
<string name="showTrackerCountInAddressBar">Show Trackers Blocked Animation </string>
<string name="tabSwitcherAnimationTileRemovalDialogTitle" instruction="A title for a dialog that shows when a user clicks a tile that shows them their current blocked tracker count">Hide tracker count?</string>
<string name="tabSwitcherAnimationTileRemovalDialogBody" instruction="A message for a dialog that shows when a user clicks a tile that shows them their current blocked tracker count">You can turn this back on in Settings > Appearance.</string>
<string name="tabSwitcherAnimationTileRemovalDialogPositiveButton">Hide</string>
Expand Down
6 changes: 6 additions & 0 deletions app/src/test/java/com/duckduckgo/app/Fakes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ class FakeSettingsDataStore :
store["clearDuckAiData"] = value
}

override var showTrackersCountInAddressBar: Boolean
get() = store["showTrackersCountInAddressBar"] as Boolean? ?: true
set(value) {
store["showTrackersCountInAddressBar"] = value
}

override fun isCurrentlySelected(clearWhatOption: ClearWhatOption): Boolean {
val currentlySelected = store["automaticallyClearWhatOption"] as ClearWhatOption?
return currentlySelected == clearWhatOption
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ internal class AppearanceViewModelTest {
@Mock
private lateinit var mockOmnibarFeatureRepository: OmnibarRepository

@Mock
private lateinit var mockAddressBarTrackersAnimationManager: com.duckduckgo.app.browser.animations.AddressBarTrackersAnimationManager

@SuppressLint("DenyListedApi")
@Before
fun before() {
Expand All @@ -92,6 +95,9 @@ internal class AppearanceViewModelTest {
whenever(mockUrlDisplayRepository.isFullUrlEnabled).thenReturn(flowOf(true))
whenever(mockTabSwitcherDataStore.isTrackersAnimationInfoTileHidden()).thenReturn(flowOf(false))
whenever(mockOmnibarFeatureRepository.isSplitOmnibarAvailable).thenReturn(false)
runTest {
whenever(mockAddressBarTrackersAnimationManager.isFeatureEnabled()).thenReturn(false)
}

initializeViewModel()
}
Expand All @@ -105,6 +111,7 @@ internal class AppearanceViewModelTest {
mockPixel,
coroutineTestRule.testDispatcherProvider,
mockTabSwitcherDataStore,
mockAddressBarTrackersAnimationManager,
mockOmnibarFeatureRepository,
)
}
Expand Down Expand Up @@ -315,6 +322,118 @@ internal class AppearanceViewModelTest {
)
}

@Test
fun `when tracker count in address bar is enabled then setting enabled`() =
runTest {
val enabled = true
testee.onShowTrackersCountInAddressBarChanged(enabled)
verify(mockAppSettingsDataStore).showTrackersCountInAddressBar = enabled
val params = mapOf(Pixel.PixelParameter.IS_ENABLED to enabled.toString())
verify(mockPixel).fire(
AppPixelName.SETTINGS_APPEARANCE_IS_TRACKER_COUNT_IN_ADDRESS_BAR_TOGGLED,
params,
emptyMap(),
Pixel.PixelType.Count,
)
}

@Test
fun `when tracker count in address bar is disabled then setting disabled`() =
runTest {
val enabled = false
testee.onShowTrackersCountInAddressBarChanged(enabled)
verify(mockAppSettingsDataStore).showTrackersCountInAddressBar = enabled
val params = mapOf(Pixel.PixelParameter.IS_ENABLED to enabled.toString())
verify(mockPixel).fire(
AppPixelName.SETTINGS_APPEARANCE_IS_TRACKER_COUNT_IN_ADDRESS_BAR_TOGGLED,
params,
emptyMap(),
Pixel.PixelType.Count,
)
}

@Test
fun `when address bar trackers animation feature flag is disabled then toggle should be hidden`() =
runTest {
whenever(mockAddressBarTrackersAnimationManager.isFeatureEnabled()).thenReturn(false)
initializeViewModel()

testee.viewState().test {
val value = expectMostRecentItem()
assertEquals(false, value.shouldShowTrackersCountInAddressBar)
cancelAndConsumeRemainingEvents()
}
}

@Test
fun `when address bar trackers animation feature flag is enabled then toggle should be visible`() =
runTest {
whenever(mockAddressBarTrackersAnimationManager.isFeatureEnabled()).thenReturn(true)
initializeViewModel()

testee.viewState().test {
val value = expectMostRecentItem()
assertEquals(true, value.shouldShowTrackersCountInAddressBar)
cancelAndConsumeRemainingEvents()
}
}

@Test
fun `when tracker count in address bar settings toggle is disabled then viewState is false`() =
runTest {
whenever(mockAppSettingsDataStore.showTrackersCountInAddressBar).thenReturn(false)
initializeViewModel()

testee.viewState().test {
val value = expectMostRecentItem()
assertEquals(false, value.isTrackersCountInAddressBarEnabled)
cancelAndConsumeRemainingEvents()
}
}

@Test
fun `when tracker count in address bar settings toggled is enabled then viewState is true`() =
runTest {
whenever(mockAppSettingsDataStore.showTrackersCountInAddressBar).thenReturn(true)
initializeViewModel()

testee.viewState().test {
val value = expectMostRecentItem()
assertEquals(true, value.isTrackersCountInAddressBarEnabled)
cancelAndConsumeRemainingEvents()
}
}

@Test
fun `when both feature flag and user preference are enabled then viewState shows both enabled`() =
runTest {
whenever(mockAddressBarTrackersAnimationManager.isFeatureEnabled()).thenReturn(true)
whenever(mockAppSettingsDataStore.showTrackersCountInAddressBar).thenReturn(true)
initializeViewModel()

testee.viewState().test {
val value = expectMostRecentItem()
assertEquals(true, value.shouldShowTrackersCountInAddressBar)
assertEquals(true, value.isTrackersCountInAddressBarEnabled)
cancelAndConsumeRemainingEvents()
}
}

@Test
fun `when feature flag is enabled but user preference is disabled then toggle is visible but unchecked`() =
runTest {
whenever(mockAddressBarTrackersAnimationManager.isFeatureEnabled()).thenReturn(true)
whenever(mockAppSettingsDataStore.showTrackersCountInAddressBar).thenReturn(false)
initializeViewModel()

testee.viewState().test {
val value = expectMostRecentItem()
assertEquals(true, value.shouldShowTrackersCountInAddressBar) // Toggle visible
assertEquals(false, value.isTrackersCountInAddressBarEnabled) // But unchecked
cancelAndConsumeRemainingEvents()
}
}

@Test
fun whenInitialisedAndLightThemeThenViewStateEmittedWithProperValues() =
runTest {
Expand Down