Skip to content

Commit

Permalink
Update the code to use the feature flags (#5526)
Browse files Browse the repository at this point in the history
Task/Issue URL:
https://app.asana.com/0/1200581511062568/1209204421122127/f

### Description
Used the feature flags to switch between the 2 animations.

Stacked PR.

---------

Co-authored-by: Dax The Translator <[email protected]>
  • Loading branch information
anikiki and daxmobile authored Feb 3, 2025
1 parent 13512ac commit 58b73ab
Show file tree
Hide file tree
Showing 57 changed files with 574 additions and 148 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import com.duckduckgo.app.browser.WebViewErrorResponse.LOADING
import com.duckduckgo.app.browser.WebViewErrorResponse.OMITTED
import com.duckduckgo.app.browser.addtohome.AddToHomeCapabilityDetector
import com.duckduckgo.app.browser.applinks.AppLinksHandler
import com.duckduckgo.app.browser.apppersonality.AppPersonalityFeature
import com.duckduckgo.app.browser.camera.CameraHardwareChecker
import com.duckduckgo.app.browser.certificates.BypassedSSLCertificatesRepository
import com.duckduckgo.app.browser.certificates.remoteconfig.SSLCertificatesFeature
Expand Down Expand Up @@ -225,6 +226,7 @@ import com.duckduckgo.privacy.config.api.ContentBlocking
import com.duckduckgo.privacy.config.api.TrackingParameters
import com.duckduckgo.privacy.config.impl.features.gpc.RealGpc.Companion.GPC_HEADER
import com.duckduckgo.privacy.config.impl.features.gpc.RealGpc.Companion.GPC_HEADER_VALUE
import com.duckduckgo.privacy.dashboard.api.PrivacyDashboardExternalPixelParams
import com.duckduckgo.privacy.dashboard.api.PrivacyProtectionTogglePlugin
import com.duckduckgo.privacy.dashboard.api.PrivacyToggleOrigin
import com.duckduckgo.privacy.dashboard.api.ui.ToggleReports
Expand Down Expand Up @@ -502,6 +504,8 @@ class BrowserTabViewModelTest {
private val mockBrokenSitePrompt: BrokenSitePrompt = mock()
private val mockTabStatsBucketing: TabStatsBucketing = mock()
private val mockDuckChatJSHelper: DuckChatJSHelper = mock()
private val fakeAppPersonalityFeature = FakeFeatureToggleFactory.create(AppPersonalityFeature::class.java)
private val mockPrivacyDashboardExternalPixelParams: PrivacyDashboardExternalPixelParams = mock()

@Before
fun before() = runTest {
Expand Down Expand Up @@ -672,6 +676,9 @@ class BrowserTabViewModelTest {
brokenSitePrompt = mockBrokenSitePrompt,
tabStatsBucketing = mockTabStatsBucketing,
maliciousSiteBlockerWebViewIntegration = mock(),
appPersonalityFeature = fakeAppPersonalityFeature,
userStageStore = mockUserStageStore,
privacyDashboardExternalPixelParams = mockPrivacyDashboardExternalPixelParams,
)

testee.loadData("abc", null, false, false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,27 @@ package com.duckduckgo.app.browser.omnibar.animations

import com.airbnb.lottie.LottieAnimationView
import com.duckduckgo.app.browser.R
import com.duckduckgo.app.browser.apppersonality.AppPersonalityFeature
import com.duckduckgo.app.global.model.PrivacyShield.PROTECTED
import com.duckduckgo.app.global.model.PrivacyShield.UNPROTECTED
import com.duckduckgo.app.global.model.PrivacyShield.WARNING
import com.duckduckgo.common.ui.store.AppTheme
import com.duckduckgo.feature.toggles.api.FakeFeatureToggleFactory
import org.junit.Test
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

class LottiePrivacyShieldAnimationHelperTest {

private val feature = FakeFeatureToggleFactory.create(AppPersonalityFeature::class.java)

@Test
fun whenLightModeAndPrivacyShieldProtectedThenSetLightShieldAnimation() {
val holder: LottieAnimationView = mock()
val appTheme: AppTheme = mock()
whenever(appTheme.isLightModeEnabled()).thenReturn(true)
val testee = LottiePrivacyShieldAnimationHelper(appTheme)
val testee = LottiePrivacyShieldAnimationHelper(appTheme, feature)

testee.setAnimationView(holder, PROTECTED)

Expand All @@ -46,7 +50,7 @@ class LottiePrivacyShieldAnimationHelperTest {
val holder: LottieAnimationView = mock()
val appTheme: AppTheme = mock()
whenever(appTheme.isLightModeEnabled()).thenReturn(false)
val testee = LottiePrivacyShieldAnimationHelper(appTheme)
val testee = LottiePrivacyShieldAnimationHelper(appTheme, feature)

testee.setAnimationView(holder, PROTECTED)

Expand All @@ -58,7 +62,7 @@ class LottiePrivacyShieldAnimationHelperTest {
val holder: LottieAnimationView = mock()
val appTheme: AppTheme = mock()
whenever(appTheme.isLightModeEnabled()).thenReturn(true)
val testee = LottiePrivacyShieldAnimationHelper(appTheme)
val testee = LottiePrivacyShieldAnimationHelper(appTheme, feature)

testee.setAnimationView(holder, UNPROTECTED)

Expand All @@ -71,7 +75,7 @@ class LottiePrivacyShieldAnimationHelperTest {
val holder: LottieAnimationView = mock()
val appTheme: AppTheme = mock()
whenever(appTheme.isLightModeEnabled()).thenReturn(false)
val testee = LottiePrivacyShieldAnimationHelper(appTheme)
val testee = LottiePrivacyShieldAnimationHelper(appTheme, feature)

testee.setAnimationView(holder, UNPROTECTED)

Expand All @@ -84,7 +88,7 @@ class LottiePrivacyShieldAnimationHelperTest {
val holder: LottieAnimationView = mock()
val appTheme: AppTheme = mock()
whenever(appTheme.isLightModeEnabled()).thenReturn(true)
val testee = LottiePrivacyShieldAnimationHelper(appTheme)
val testee = LottiePrivacyShieldAnimationHelper(appTheme, feature)

testee.setAnimationView(holder, WARNING)

Expand All @@ -97,7 +101,7 @@ class LottiePrivacyShieldAnimationHelperTest {
val holder: LottieAnimationView = mock()
val appTheme: AppTheme = mock()
whenever(appTheme.isLightModeEnabled()).thenReturn(false)
val testee = LottiePrivacyShieldAnimationHelper(appTheme)
val testee = LottiePrivacyShieldAnimationHelper(appTheme, feature)

testee.setAnimationView(holder, WARNING)

Expand Down
63 changes: 54 additions & 9 deletions app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import androidx.core.net.toUri
import androidx.core.text.HtmlCompat
import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY
import androidx.core.text.toSpannable
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.postDelayed
import androidx.fragment.app.DialogFragment
Expand Down Expand Up @@ -103,11 +104,12 @@ import com.duckduckgo.app.browser.R.string
import com.duckduckgo.app.browser.SSLErrorType.NONE
import com.duckduckgo.app.browser.WebViewErrorResponse.LOADING
import com.duckduckgo.app.browser.WebViewErrorResponse.OMITTED
import com.duckduckgo.app.browser.animations.TrackersCircleAnimationHelper
import com.duckduckgo.app.browser.animations.ExperimentTrackersAnimationHelper
import com.duckduckgo.app.browser.api.WebViewCapabilityChecker
import com.duckduckgo.app.browser.api.WebViewCapabilityChecker.WebViewCapability
import com.duckduckgo.app.browser.applinks.AppLinksLauncher
import com.duckduckgo.app.browser.applinks.AppLinksSnackBarConfigurator
import com.duckduckgo.app.browser.apppersonality.AppPersonalityFeature
import com.duckduckgo.app.browser.autocomplete.BrowserAutoCompleteSuggestionsAdapter
import com.duckduckgo.app.browser.autocomplete.SuggestionItemDecoration
import com.duckduckgo.app.browser.commands.Command
Expand Down Expand Up @@ -531,7 +533,10 @@ class BrowserTabFragment :
lateinit var webViewCapabilityChecker: WebViewCapabilityChecker

@Inject
lateinit var animatorHelper: TrackersCircleAnimationHelper
lateinit var experimentTrackersAnimationHelper: ExperimentTrackersAnimationHelper

@Inject
lateinit var appPersonalityFeature: AppPersonalityFeature

/**
* We use this to monitor whether the user was seeing the in-context Email Protection signup prompt
Expand Down Expand Up @@ -659,7 +664,14 @@ class BrowserTabFragment :
delay(COOKIES_ANIMATION_DELAY)
}
context?.let {
omnibar.createCookiesAnimation(isCosmetic)
if (appPersonalityFeature.self().isEnabled() &&
appPersonalityFeature.trackersBlockedAnimation().isEnabled() &&
viewModel.trackersCount().isNotEmpty()
) {
omnibar.enqueueCookiesAnimation(isCosmetic)
} else {
omnibar.createCookiesAnimation(isCosmetic)
}
}
}
}
Expand Down Expand Up @@ -795,16 +807,27 @@ class BrowserTabFragment :

private lateinit var privacyProtectionsPopup: PrivacyProtectionsPopup

private fun showNewTrackersBlockingAnimation(logos: List<TrackerLogo>) {
animatorHelper.startTrackersCircleAnimation(
private fun showExperimentTrackersBurstAnimation(logos: List<TrackerLogo>) {
experimentTrackersAnimationHelper.startTrackersBurstAnimation(
context = requireContext(),
trackersCircleAnimationView = binding.newTrackersBlockingAnimationView,
trackersBurstAnimationView = binding.trackersBurstAnimationView,
omnibarShieldAnimationView = omnibar.shieldIcon,
omnibarPosition = omnibar.omnibarPosition,
omnibarView = if (omnibar.omnibarPosition == OmnibarPosition.TOP) {
binding.newOmnibar
} else {
binding.newOmnibarBottom
},
logos = logos,
)
}

private fun showExperimentShieldPopAnimation() {
experimentTrackersAnimationHelper.startShieldPopAnimation(
omnibarShieldAnimationView = omnibar.shieldIcon,
)
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Timber.d("onCreate called for tabId=$tabId")
Expand Down Expand Up @@ -904,6 +927,10 @@ class BrowserTabFragment :
}

private fun configureTrackersBlockedSlidingView() {
if (!appPersonalityFeature.self().isEnabled() || !appPersonalityFeature.trackersBlockedAnimation().isEnabled()) {
return
}

val displayMetrics = resources.displayMetrics
val layoutParams = binding.trackersBlockedSlidingView.layoutParams as CoordinatorLayout.LayoutParams
when (omnibar.omnibarPosition) {
Expand Down Expand Up @@ -932,15 +959,31 @@ class BrowserTabFragment :
}

private fun notifyVerticalOffsetChanged(scrollFraction: Float) {
// Ensure the trackersBlockedSlidingView is hidden on new tab or when scrolling is disabled.
if (binding.trackersBlockedSlidingView.isVisible && (binding.browserLayout.isGone || !binding.newOmnibar.isOmnibarScrollingEnabled())) {
binding.trackersBlockedSlidingView.hide()
return
}

if (!viewModel.isSiteProtected() || scrollFraction == 1.0f) {
return
}

// Move the trackersBlockedSlidingView in sync with the top omnibar.
binding.trackersBlockedSlidingView.translationY = -binding.trackersBlockedSlidingView.height * (1 - scrollFraction)
if (scrollFraction == 0.0f) {
binding.trackersBlockedSlidingView.gone()
} else {
if (binding.trackersBurstAnimationView.isAnimating) {
binding.trackersBurstAnimationView.cancelAnimation()
}
val count = viewModel.trackersCount()
if (count != binding.trackers.text) {
binding.trackers.text = count
}
binding.website.text = viewModel.url?.extractDomain()
binding.trackersBlockedSlidingView.show()
}
binding.trackers.text = viewModel.trackersCount().toString()
binding.website.text = viewModel.url?.extractDomain()
}

private fun onOmnibarTabsButtonPressed() {
Expand Down Expand Up @@ -1203,6 +1246,7 @@ class BrowserTabFragment :

override fun onStop() {
alertDialog?.dismiss()
experimentTrackersAnimationHelper.cancelAnimations()
super.onStop()
}

Expand Down Expand Up @@ -1865,7 +1909,8 @@ class BrowserTabFragment :
binding.autoCompleteSuggestionsList.gone()
browserActivity?.openExistingTab(it.tabId)
}
is Command.StartTrackersLogosAnimation -> showNewTrackersBlockingAnimation(it.logos)
is Command.StartExperimentTrackersBurstAnimation -> showExperimentTrackersBurstAnimation(it.logos)
is Command.StartExperimentShieldPopAnimation -> showExperimentShieldPopAnimation()
else -> {
// NO OP
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import com.duckduckgo.app.browser.WebViewErrorResponse.LOADING
import com.duckduckgo.app.browser.WebViewErrorResponse.OMITTED
import com.duckduckgo.app.browser.addtohome.AddToHomeCapabilityDetector
import com.duckduckgo.app.browser.applinks.AppLinksHandler
import com.duckduckgo.app.browser.apppersonality.AppPersonalityFeature
import com.duckduckgo.app.browser.camera.CameraHardwareChecker
import com.duckduckgo.app.browser.certificates.BypassedSSLCertificatesRepository
import com.duckduckgo.app.browser.certificates.remoteconfig.SSLCertificatesFeature
Expand Down Expand Up @@ -236,6 +237,8 @@ import com.duckduckgo.app.global.model.SiteFactory
import com.duckduckgo.app.global.model.domain
import com.duckduckgo.app.global.model.domainMatchesUrl
import com.duckduckgo.app.location.data.LocationPermissionType
import com.duckduckgo.app.onboarding.store.AppStage
import com.duckduckgo.app.onboarding.store.UserStageStore
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.HighlightsOnboardingExperimentManager
import com.duckduckgo.app.pixels.AppPixelName
import com.duckduckgo.app.pixels.AppPixelName.AUTOCOMPLETE_BANNER_DISMISSED
Expand All @@ -257,6 +260,8 @@ import com.duckduckgo.app.settings.db.SettingsDataStore
import com.duckduckgo.app.statistics.api.StatisticsUpdater
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.app.statistics.pixels.Pixel.PixelParameter
import com.duckduckgo.app.statistics.pixels.Pixel.PixelParameter.AFTER_BURST_ANIMATION
import com.duckduckgo.app.statistics.pixels.Pixel.PixelParameter.TRACKERS_ANIMATION_SHOWN_DURING_ONBOARDING
import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Count
import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Daily
import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Unique
Expand Down Expand Up @@ -304,6 +309,7 @@ import com.duckduckgo.privacy.config.api.AmpLinkInfo
import com.duckduckgo.privacy.config.api.AmpLinks
import com.duckduckgo.privacy.config.api.ContentBlocking
import com.duckduckgo.privacy.config.api.TrackingParameters
import com.duckduckgo.privacy.dashboard.api.PrivacyDashboardExternalPixelParams
import com.duckduckgo.privacy.dashboard.api.PrivacyProtectionTogglePlugin
import com.duckduckgo.privacy.dashboard.api.PrivacyToggleOrigin
import com.duckduckgo.privacy.dashboard.api.ui.DashboardOpener
Expand Down Expand Up @@ -458,6 +464,9 @@ class BrowserTabViewModel @Inject constructor(
private val brokenSitePrompt: BrokenSitePrompt,
private val tabStatsBucketing: TabStatsBucketing,
private val maliciousSiteBlockerWebViewIntegration: MaliciousSiteBlockerWebViewIntegration,
private val appPersonalityFeature: AppPersonalityFeature,
private val userStageStore: UserStageStore,
private val privacyDashboardExternalPixelParams: PrivacyDashboardExternalPixelParams,
) : WebViewClientListener,
EditSavedSiteListener,
DeleteBookmarkListener,
Expand Down Expand Up @@ -1480,6 +1489,7 @@ class BrowserTabViewModel @Inject constructor(
) {
Timber.v("Page changed: $url")
cleanupBlobDownloadReplyProxyMaps()
privacyDashboardExternalPixelParams.clearPixelParams()

hasCtaBeenShownForCurrentPage.set(false)
buildSiteFactory(url, title, urlUnchangedForExternalLaunchPurposes(site?.url, url))
Expand Down Expand Up @@ -1896,6 +1906,7 @@ class BrowserTabViewModel @Inject constructor(
val privacyProtection: PrivacyShield = withContext(dispatchers.io()) {
site?.privacyProtection() ?: PrivacyShield.UNKNOWN
}

Timber.i("Shield: privacyProtection $privacyProtection")
withContext(dispatchers.main()) {
siteLiveData.value = site
Expand Down Expand Up @@ -3091,6 +3102,7 @@ class BrowserTabViewModel @Inject constructor(
}

fun onWebViewRefreshed() {
site?.resetTrackingEvents()
refreshBrowserError()
resetAutoConsent()
accessibilityViewState.value = currentAccessibilityViewState().copy(refreshWebView = false)
Expand Down Expand Up @@ -3788,10 +3800,32 @@ class BrowserTabViewModel @Inject constructor(
}

fun onAnimationFinished(logos: List<TrackerLogo>) {
command.value = Command.StartTrackersLogosAnimation(logos)
if (logos.isEmpty()) {
return
}

if (appPersonalityFeature.self().isEnabled() && appPersonalityFeature.trackersBlockedAnimation().isEnabled()) {
if (logos.size > TRACKER_LOGO_ANIMATION_THRESHOLD) {
command.value = Command.StartExperimentTrackersBurstAnimation(logos)
viewModelScope.launch {
pixel.fire(
AppPixelName.TRACKERS_BURST_ANIMATION_SHOWN,
mapOf(TRACKERS_ANIMATION_SHOWN_DURING_ONBOARDING to "${userStageStore.getUserAppStage() != AppStage.ESTABLISHED}"),
)
privacyDashboardExternalPixelParams.setPixelParams(AFTER_BURST_ANIMATION, "true")
}
} else {
command.value = Command.StartExperimentShieldPopAnimation
}
}
}

fun trackersCount(): Int = site?.trackerCount ?: 0
fun trackersCount(): String = site?.trackerCount?.takeIf { it > 0 }?.toString() ?: ""

fun isSiteProtected(): Boolean {
val shield = site?.privacyProtection() ?: PrivacyShield.UNKNOWN
return shield == PrivacyShield.PROTECTED
}

companion object {
private const val FIXED_PROGRESS = 50
Expand All @@ -3806,6 +3840,8 @@ class BrowserTabViewModel @Inject constructor(
private const val HTTP_STATUS_CODE_CLIENT_ERROR_PREFIX = 4 // 4xx, client error status code prefix
private const val HTTP_STATUS_CODE_SERVER_ERROR_PREFIX = 5 // 5xx, server error status code prefix

private const val TRACKER_LOGO_ANIMATION_THRESHOLD = 2

// https://www.iso.org/iso-3166-country-codes.html
private val PRINT_LETTER_FORMAT_COUNTRIES_ISO3166_2 = setOf(
Locale.US.country,
Expand Down
Loading

0 comments on commit 58b73ab

Please sign in to comment.