Skip to content

Commit a4450b2

Browse files
committed
Add toggle to control tracker number animation
1 parent 42191d1 commit a4450b2

11 files changed

Lines changed: 338 additions & 2 deletions

File tree

app/src/main/java/com/duckduckgo/app/appearance/AppearanceActivity.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ class AppearanceActivity : DuckDuckGoActivity() {
100100
viewModel.onShowTrackersCountInTabSwitcherChanged(isChecked)
101101
}
102102

103+
private val showTrackersCountInAddressBar =
104+
CompoundButton.OnCheckedChangeListener { _, isChecked ->
105+
viewModel.onShowTrackersCountInAddressBarChanged(isChecked)
106+
}
107+
103108
private val changeIconFlow =
104109
registerForActivityResult(ChangeIconContract()) { resultOk ->
105110
if (resultOk) {
@@ -173,6 +178,12 @@ class AppearanceActivity : DuckDuckGoActivity() {
173178
viewState.isTrackersCountInTabSwitcherEnabled,
174179
showTrackersCountInTabSwitcher,
175180
)
181+
binding.showTrackersCountInAddressBar.isVisible = viewState.shouldShowTrackersCountInAddressBar
182+
binding.trackersCountInAddressBarDivider.isVisible = viewState.shouldShowTrackersCountInAddressBar
183+
binding.showTrackersCountInAddressBar.quietlySetIsChecked(
184+
viewState.isTrackersCountInAddressBarEnabled,
185+
showTrackersCountInAddressBar,
186+
)
176187
configureOmnibarSettings(it)
177188
}
178189
}.launchIn(lifecycleScope)

app/src/main/java/com/duckduckgo/app/appearance/AppearanceViewModel.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import androidx.lifecycle.ViewModel
2020
import androidx.lifecycle.viewModelScope
2121
import androidx.webkit.WebViewFeature
2222
import com.duckduckgo.anvil.annotations.ContributesViewModel
23+
import com.duckduckgo.app.browser.animations.AddressBarTrackersAnimationManager
2324
import com.duckduckgo.app.browser.api.OmnibarRepository
2425
import com.duckduckgo.app.browser.omnibar.OmnibarType
2526
import com.duckduckgo.app.browser.urldisplay.UrlDisplayRepository
@@ -58,6 +59,7 @@ class AppearanceViewModel @Inject constructor(
5859
private val pixel: Pixel,
5960
private val dispatcherProvider: DispatcherProvider,
6061
private val tabSwitcherDataStore: TabSwitcherDataStore,
62+
private val addressBarTrackersAnimationManager: AddressBarTrackersAnimationManager,
6163
omnibarRepository: OmnibarRepository,
6264
) : ViewModel() {
6365
data class ViewState(
@@ -69,6 +71,8 @@ class AppearanceViewModel @Inject constructor(
6971
val omnibarType: OmnibarType = OmnibarType.SINGLE_TOP,
7072
val isFullUrlEnabled: Boolean = true,
7173
val isTrackersCountInTabSwitcherEnabled: Boolean = true,
74+
val isTrackersCountInAddressBarEnabled: Boolean = true,
75+
val shouldShowTrackersCountInAddressBar: Boolean = false,
7276
val shouldShowSplitOmnibarSettings: Boolean = false,
7377
)
7478

@@ -95,6 +99,7 @@ class AppearanceViewModel @Inject constructor(
9599
supportsForceDarkMode = WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING),
96100
omnibarType = settingsDataStore.omnibarType,
97101
shouldShowSplitOmnibarSettings = omnibarRepository.isSplitOmnibarAvailable,
102+
isTrackersCountInAddressBarEnabled = settingsDataStore.showTrackersCountInAddressBar,
98103
),
99104
)
100105

@@ -103,9 +108,11 @@ class AppearanceViewModel @Inject constructor(
103108
urlDisplayRepository.isFullUrlEnabled,
104109
tabSwitcherDataStore.isTrackersAnimationInfoTileHidden(),
105110
) { currentViewState, isFullUrlEnabled, isTrackersAnimationTileHidden ->
111+
val isFeatureEnabled = addressBarTrackersAnimationManager.isFeatureEnabled()
106112
currentViewState.copy(
107113
isTrackersCountInTabSwitcherEnabled = !isTrackersAnimationTileHidden,
108114
isFullUrlEnabled = isFullUrlEnabled,
115+
shouldShowTrackersCountInAddressBar = isFeatureEnabled,
109116
)
110117
}.stateIn(viewModelScope, SharingStarted.Lazily, viewState.value)
111118

@@ -194,4 +201,14 @@ class AppearanceViewModel @Inject constructor(
194201
pixel.fire(AppPixelName.SETTINGS_APPEARANCE_IS_TRACKER_COUNT_IN_TAB_SWITCHER_TOGGLED, params)
195202
}
196203
}
204+
205+
fun onShowTrackersCountInAddressBarChanged(checked: Boolean) {
206+
viewModelScope.launch(dispatcherProvider.io()) {
207+
settingsDataStore.showTrackersCountInAddressBar = checked
208+
viewState.update { it.copy(isTrackersCountInAddressBarEnabled = checked) }
209+
210+
val params = mapOf(Pixel.PixelParameter.IS_ENABLED to checked.toString())
211+
pixel.fire(AppPixelName.SETTINGS_APPEARANCE_IS_TRACKER_COUNT_IN_ADDRESS_BAR_TOGGLED, params)
212+
}
213+
}
197214
}

app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayoutViewModel.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ class OmnibarLayoutViewModel @Inject constructor(
137137
hasUnreadTabs = tabs.firstOrNull { !it.viewed } != null,
138138
showBrowserMenuHighlight = highlightOverflowMenu,
139139
viewMode = getViewMode(state),
140-
isAddressBarTrackersAnimationEnabled = isAddressBarTrackersAnimationEnabled,
140+
isAddressBarTrackersAnimationEnabled = isAddressBarTrackersAnimationEnabled &&
141+
settingsDataStore.showTrackersCountInAddressBar,
141142
)
142143
}.flowOn(dispatcherProvider.io()).stateIn(viewModelScope, SharingStarted.Eagerly, _viewState.value)
143144

@@ -903,7 +904,7 @@ class OmnibarLayoutViewModel @Inject constructor(
903904
Command.StartTrackersAnimation(
904905
entities = decoration.entities,
905906
isCustomTab = viewState.value.viewMode is CustomTab,
906-
isAddressBarTrackersAnimationEnabled = addressBarTrackersAnimationManager.isFeatureEnabled(),
907+
isAddressBarTrackersAnimationEnabled = viewState.value.isAddressBarTrackersAnimationEnabled,
907908
),
908909
)
909910
}

app/src/main/java/com/duckduckgo/app/global/api/PixelParamRemovalInterceptor.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ object PixelInterceptorPixelsRequiringDataCleaning : PixelParamRemovalPlugin {
127127
AppPixelName.SEARCH_WIDGET_ADDED.pixelName to PixelParameter.removeAtb(),
128128
AppPixelName.SEARCH_WIDGET_DELETED.pixelName to PixelParameter.removeAtb(),
129129
AppPixelName.SETTINGS_APPEARANCE_IS_TRACKER_COUNT_IN_TAB_SWITCHER_TOGGLED.pixelName to PixelParameter.removeAll(),
130+
AppPixelName.SETTINGS_APPEARANCE_IS_TRACKER_COUNT_IN_ADDRESS_BAR_TOGGLED.pixelName to PixelParameter.removeAll(),
130131
AppPixelName.TIMEOUT_WAITING_FOR_APP_REFERRER.pixelName to PixelParameter.removeAtb(),
131132
AppPixelName.PRODUCT_TELEMETRY_SURFACE_LANDSCAPE_ORIENTATION_USED.pixelName to PixelParameter.removeAtb(),
132133
AppPixelName.PRODUCT_TELEMETRY_SURFACE_LANDSCAPE_ORIENTATION_USED_DAILY.pixelName to PixelParameter.removeAtb(),

app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ enum class AppPixelName(override val pixelName: String) : Pixel.PixelName {
143143
SETTINGS_APPEARANCE_IS_FULL_URL_OPTION_TOGGLED("m_appearance_settings_is_full_url_option_toggled"),
144144
APPEARANCE_SETTINGS_IS_FULL_URL_ENABLED_DAILY("m_appearance_settings_is_full_url_enabled_daily"),
145145
SETTINGS_APPEARANCE_IS_TRACKER_COUNT_IN_TAB_SWITCHER_TOGGLED("m_appearance_settings_is_tracker_count_in_tab_switcher_toggled"),
146+
SETTINGS_APPEARANCE_IS_TRACKER_COUNT_IN_ADDRESS_BAR_TOGGLED("m_appearance_settings_is_tracker_count_in_address_bar_toggled"),
146147
SETTINGS_APP_ICON_PRESSED("ms_app_icon_setting_pressed"),
147148
SETTINGS_ADDRESS_BAR_POSITION_PRESSED("ms_address_bar_position_setting_pressed"),
148149
SETTINGS_ADDRESS_BAR_POSITION_SELECTED_TOP("ms_address_bar_position_setting_selected_top"),

app/src/main/java/com/duckduckgo/app/settings/db/SettingsDataStore.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ interface SettingsDataStore {
105105
*/
106106
var urlPreferenceSetByUser: Boolean
107107
var clearDuckAiData: Boolean
108+
var showTrackersCountInAddressBar: Boolean
108109

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

271+
override var showTrackersCountInAddressBar: Boolean
272+
get() = preferences.getBoolean(KEY_SHOW_TRACKERS_COUNT_IN_ADDRESS_BAR, true)
273+
set(enabled) = preferences.edit { putBoolean(KEY_SHOW_TRACKERS_COUNT_IN_ADDRESS_BAR, enabled) }
274+
270275
override fun hasBackgroundTimestampRecorded(): Boolean = preferences.contains(KEY_APP_BACKGROUNDED_TIMESTAMP)
271276

272277
override fun clearAppBackgroundTimestamp() = preferences.edit { remove(KEY_APP_BACKGROUNDED_TIMESTAMP) }
@@ -345,6 +350,7 @@ class SettingsSharedPreferences @Inject constructor(
345350
const val URL_PREFERENCE_MIGRATED = "URL_PREFERENCE_MIGRATED"
346351
const val URL_PREFERENCE_SET_BY_USER = "URL_PREFERENCE_SET_BY_USER"
347352
const val KEY_CLEAR_DUCK_AI_DATA = "KEY_CLEAR_DUCK_AI_DATA"
353+
const val KEY_SHOW_TRACKERS_COUNT_IN_ADDRESS_BAR = "KEY_SHOW_TRACKERS_COUNT_IN_ADDRESS_BAR"
348354
}
349355

350356
private class FireAnimationPrefsMapper {

app/src/main/res/layout/activity_appearance.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,20 @@
274274
app:primaryTextTruncated="false"
275275
app:showSwitch="true" />
276276

277+
<com.duckduckgo.common.ui.view.divider.HorizontalDivider
278+
android:id="@+id/trackersCountInAddressBarDivider"
279+
android:layout_width="match_parent"
280+
android:layout_height="wrap_content"
281+
android:paddingBottom="0dp" />
282+
283+
<com.duckduckgo.common.ui.view.listitem.OneLineListItem
284+
android:id="@+id/showTrackersCountInAddressBar"
285+
android:layout_width="match_parent"
286+
android:layout_height="wrap_content"
287+
app:primaryText="@string/showTrackerCountInAddressBar"
288+
app:primaryTextTruncated="false"
289+
app:showSwitch="true" />
290+
277291
<com.duckduckgo.common.ui.view.divider.HorizontalDivider
278292
android:id="@+id/trackersAnimationSettingDivider"
279293
android:layout_width="match_parent"

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,7 @@
893893

894894
<!--TabSwitcher Trackers Animation-->
895895
<string name="showTrackerCountInTabSwitcher">Show Tracker Count in Tab Switcher</string>
896+
<string name="showTrackerCountInAddressBar">Show Trackers Blocked Animation </string>
896897
<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>
897898
<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>
898899
<string name="tabSwitcherAnimationTileRemovalDialogPositiveButton">Hide</string>

app/src/test/java/com/duckduckgo/app/Fakes.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,12 @@ class FakeSettingsDataStore :
214214
store["clearDuckAiData"] = value
215215
}
216216

217+
override var showTrackersCountInAddressBar: Boolean
218+
get() = store["showTrackersCountInAddressBar"] as Boolean? ?: true
219+
set(value) {
220+
store["showTrackersCountInAddressBar"] = value
221+
}
222+
217223
override fun isCurrentlySelected(clearWhatOption: ClearWhatOption): Boolean {
218224
val currentlySelected = store["automaticallyClearWhatOption"] as ClearWhatOption?
219225
return currentlySelected == clearWhatOption

app/src/test/java/com/duckduckgo/app/appearance/AppearanceViewModelTest.kt

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ internal class AppearanceViewModelTest {
8080
@Mock
8181
private lateinit var mockOmnibarFeatureRepository: OmnibarRepository
8282

83+
@Mock
84+
private lateinit var mockAddressBarTrackersAnimationManager: com.duckduckgo.app.browser.animations.AddressBarTrackersAnimationManager
85+
8386
@SuppressLint("DenyListedApi")
8487
@Before
8588
fun before() {
@@ -92,6 +95,9 @@ internal class AppearanceViewModelTest {
9295
whenever(mockUrlDisplayRepository.isFullUrlEnabled).thenReturn(flowOf(true))
9396
whenever(mockTabSwitcherDataStore.isTrackersAnimationInfoTileHidden()).thenReturn(flowOf(false))
9497
whenever(mockOmnibarFeatureRepository.isSplitOmnibarAvailable).thenReturn(false)
98+
runTest {
99+
whenever(mockAddressBarTrackersAnimationManager.isFeatureEnabled()).thenReturn(false)
100+
}
95101

96102
initializeViewModel()
97103
}
@@ -105,6 +111,7 @@ internal class AppearanceViewModelTest {
105111
mockPixel,
106112
coroutineTestRule.testDispatcherProvider,
107113
mockTabSwitcherDataStore,
114+
mockAddressBarTrackersAnimationManager,
108115
mockOmnibarFeatureRepository,
109116
)
110117
}
@@ -315,6 +322,143 @@ internal class AppearanceViewModelTest {
315322
)
316323
}
317324

325+
@Test
326+
fun `when tracker count in address bar is enabled then setting enabled`() =
327+
runTest {
328+
val enabled = true
329+
testee.onShowTrackersCountInAddressBarChanged(enabled)
330+
verify(mockAppSettingsDataStore).showTrackersCountInAddressBar = enabled
331+
val params = mapOf(Pixel.PixelParameter.IS_ENABLED to enabled.toString())
332+
verify(mockPixel).fire(
333+
AppPixelName.SETTINGS_APPEARANCE_IS_TRACKER_COUNT_IN_ADDRESS_BAR_TOGGLED,
334+
params,
335+
emptyMap(),
336+
Pixel.PixelType.Count,
337+
)
338+
}
339+
340+
@Test
341+
fun `when tracker count in address bar is disabled then setting disabled`() =
342+
runTest {
343+
val enabled = false
344+
testee.onShowTrackersCountInAddressBarChanged(enabled)
345+
verify(mockAppSettingsDataStore).showTrackersCountInAddressBar = enabled
346+
val params = mapOf(Pixel.PixelParameter.IS_ENABLED to enabled.toString())
347+
verify(mockPixel).fire(
348+
AppPixelName.SETTINGS_APPEARANCE_IS_TRACKER_COUNT_IN_ADDRESS_BAR_TOGGLED,
349+
params,
350+
emptyMap(),
351+
Pixel.PixelType.Count,
352+
)
353+
}
354+
355+
@Test
356+
fun `when address bar trackers animation feature is disabled then toggle should be hidden`() =
357+
runTest {
358+
whenever(mockAddressBarTrackersAnimationManager.isFeatureEnabled()).thenReturn(false)
359+
initializeViewModel()
360+
361+
testee.viewState().test {
362+
val value = expectMostRecentItem()
363+
assertEquals(false, value.shouldShowTrackersCountInAddressBar)
364+
cancelAndConsumeRemainingEvents()
365+
}
366+
}
367+
368+
@Test
369+
fun `when address bar trackers animation feature is enabled then toggle should be visible`() =
370+
runTest {
371+
whenever(mockAddressBarTrackersAnimationManager.isFeatureEnabled()).thenReturn(true)
372+
initializeViewModel()
373+
374+
testee.viewState().test {
375+
val value = expectMostRecentItem()
376+
assertEquals(true, value.shouldShowTrackersCountInAddressBar)
377+
cancelAndConsumeRemainingEvents()
378+
}
379+
}
380+
381+
@Test
382+
fun `when tracker count in address bar setting is stored then it persists correctly`() =
383+
runTest {
384+
whenever(mockAppSettingsDataStore.showTrackersCountInAddressBar).thenReturn(false)
385+
initializeViewModel()
386+
387+
testee.viewState().test {
388+
val value = expectMostRecentItem()
389+
assertEquals(false, value.isTrackersCountInAddressBarEnabled)
390+
cancelAndConsumeRemainingEvents()
391+
}
392+
}
393+
394+
@Test
395+
fun `when tracker count in address bar is enabled by default then viewState reflects it`() =
396+
runTest {
397+
whenever(mockAppSettingsDataStore.showTrackersCountInAddressBar).thenReturn(true)
398+
initializeViewModel()
399+
400+
testee.viewState().test {
401+
val value = expectMostRecentItem()
402+
assertEquals(true, value.isTrackersCountInAddressBarEnabled)
403+
cancelAndConsumeRemainingEvents()
404+
}
405+
}
406+
407+
@Test
408+
fun `when both feature flag and user preference are enabled then viewState shows both enabled`() =
409+
runTest {
410+
whenever(mockAddressBarTrackersAnimationManager.isFeatureEnabled()).thenReturn(true)
411+
whenever(mockAppSettingsDataStore.showTrackersCountInAddressBar).thenReturn(true)
412+
initializeViewModel()
413+
414+
testee.viewState().test {
415+
val value = expectMostRecentItem()
416+
assertEquals(true, value.shouldShowTrackersCountInAddressBar)
417+
assertEquals(true, value.isTrackersCountInAddressBarEnabled)
418+
cancelAndConsumeRemainingEvents()
419+
}
420+
}
421+
422+
@Test
423+
fun `when feature flag is enabled but user preference is disabled then toggle is visible but unchecked`() =
424+
runTest {
425+
whenever(mockAddressBarTrackersAnimationManager.isFeatureEnabled()).thenReturn(true)
426+
whenever(mockAppSettingsDataStore.showTrackersCountInAddressBar).thenReturn(false)
427+
initializeViewModel()
428+
429+
testee.viewState().test {
430+
val value = expectMostRecentItem()
431+
assertEquals(true, value.shouldShowTrackersCountInAddressBar) // Toggle visible
432+
assertEquals(false, value.isTrackersCountInAddressBarEnabled) // But unchecked
433+
cancelAndConsumeRemainingEvents()
434+
}
435+
}
436+
437+
@Test
438+
fun `when tracker count in address bar is changed then viewState updates`() =
439+
runTest {
440+
testee.onShowTrackersCountInAddressBarChanged(false)
441+
442+
testee.viewState().test {
443+
val value = expectMostRecentItem()
444+
assertEquals(false, value.isTrackersCountInAddressBarEnabled)
445+
cancelAndConsumeRemainingEvents()
446+
}
447+
}
448+
449+
@Test
450+
fun `when tracker count in address bar is enabled then datastore is updated`() =
451+
runTest {
452+
testee.onShowTrackersCountInAddressBarChanged(true)
453+
454+
// Wait for coroutine to complete
455+
testee.viewState().test {
456+
awaitItem()
457+
verify(mockAppSettingsDataStore).showTrackersCountInAddressBar = true
458+
cancelAndConsumeRemainingEvents()
459+
}
460+
}
461+
318462
@Test
319463
fun whenInitialisedAndLightThemeThenViewStateEmittedWithProperValues() =
320464
runTest {

0 commit comments

Comments
 (0)