Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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 @@ -59,6 +59,7 @@ import com.duckduckgo.app.onboarding.store.OnboardingStore
import com.duckduckgo.app.onboarding.store.UserStageStore
import com.duckduckgo.app.privacy.db.NetworkLeaderboardDao
import com.duckduckgo.app.privacy.db.UserWhitelistDao
import com.duckduckgo.app.privacy.model.PrivacyGrade
import com.duckduckgo.app.privacy.model.PrivacyPractices
import com.duckduckgo.app.privacy.model.TestEntity
import com.duckduckgo.app.privacy.model.UserWhitelistedDomain
Expand Down Expand Up @@ -584,26 +585,89 @@ class BrowserTabViewModelTest {
@Test
fun whenUrlClearedThenPrivacyGradeIsCleared() = coroutineRule.runBlocking {
loadUrl("https://duckduckgo.com")
assertNotNull(testee.privacyGrade.value)
assertNotNull(privacyGradeState().privacyGrade)
loadUrl(null)
assertNull(testee.privacyGrade.value)
assertNull(privacyGradeState().privacyGrade)
}

@Test
fun whenUrlLoadedThenPrivacyGradeIsReset() = coroutineRule.runBlocking {
loadUrl("https://duckduckgo.com")
assertNotNull(testee.privacyGrade.value)
assertNotNull(privacyGradeState().privacyGrade)
}

@Test
fun whenEnoughTrackersDetectedThenPrivacyGradeIsUpdated() {
val grade = testee.privacyGrade.value
val grade = privacyGradeState().privacyGrade
loadUrl("https://example.com")
val entity = TestEntity("Network1", "Network1", 10.0)
for (i in 1..10) {
testee.trackerDetected(TrackingEvent("https://example.com", "", null, entity, false))
}
assertNotEquals(grade, testee.privacyGrade.value)
assertNotEquals(grade, privacyGradeState().privacyGrade)
}

@Test
fun whenPrivacyGradeFinishedLoadingThenDoNotShowLoadingGrade() {
testee.stopShowingEmptyGrade()
assertFalse(privacyGradeState().showEmptyGrade)
}

@Test
fun whenProgressChangesWhileBrowsingButSiteNotFullyLoadedThenPrivacyGradeShouldAnimateIsTrue() {
setBrowserShowing(true)
testee.progressChanged(50)
assertTrue(privacyGradeState().shouldAnimate)
}

@Test
fun whenProgressChangesWhileBrowsingAndSiteIsFullyLoadedThenPrivacyGradeShouldAnimateIsFalse() {
setBrowserShowing(true)
testee.progressChanged(100)
assertFalse(privacyGradeState().shouldAnimate)
}

@Test
fun whenProgressChangesAndPrivacyIsOnThenShowLoadingGradeIsAlwaysTrue() {
setBrowserShowing(true)
testee.progressChanged(50)
assertTrue(privacyGradeState().showEmptyGrade)
testee.progressChanged(100)
assertTrue(privacyGradeState().showEmptyGrade)
}

@Test
fun whenProgressChangesAndPrivacyIsOffButSiteNotFullyLoadedThenShowLoadingGradeIsTrue() {
setBrowserShowing(true)
testee.loadingViewState.value = loadingViewState().copy(privacyOn = false)
testee.progressChanged(50)
assertTrue(privacyGradeState().showEmptyGrade)
}

@Test
fun whenProgressChangesAndPrivacyIsOffAndSiteIsFullyLoadedThenShowLoadingGradeIsFalse() {
setBrowserShowing(true)
testee.loadingViewState.value = loadingViewState().copy(privacyOn = false)
testee.progressChanged(100)
assertFalse(privacyGradeState().showEmptyGrade)
}

@Test
fun whenNotShowingEmptyGradeAndPrivacyGradeIsNotUnknownThenIsEnableIsTrue() {
val testee = BrowserTabViewModel.PrivacyGradeViewState(PrivacyGrade.A, shouldAnimate = false, showEmptyGrade = false)
assertTrue(testee.isEnabled)
}

@Test
fun whenPrivacyGradeIsUnknownThenIsEnableIsFalse() {
val testee = BrowserTabViewModel.PrivacyGradeViewState(PrivacyGrade.UNKNOWN, shouldAnimate = false, showEmptyGrade = false)
assertFalse(testee.isEnabled)
}

@Test
fun whenShowEmptyGradeIsTrueThenIsEnableIsFalse() {
val testee = BrowserTabViewModel.PrivacyGradeViewState(PrivacyGrade.A, shouldAnimate = false, showEmptyGrade = true)
assertFalse(testee.isEnabled)
}

@Test
Expand All @@ -617,6 +681,12 @@ class BrowserTabViewModelTest {
assertTrue(browserViewState().showPrivacyGrade)
}

@Test
fun whenOmnibarDoesNotHaveFocusThenShowEmptyGradeIsFalse() {
testee.onOmnibarInputStateChanged(query = "", hasFocus = false, hasQueryChanged = false)
assertFalse(privacyGradeState().showEmptyGrade)
}

@Test
fun whenOmnibarDoesNotHaveFocusThenPrivacyGradeIsShownAndSearchIconIsHidden() {
testee.onOmnibarInputStateChanged(query = "", hasFocus = false, hasQueryChanged = false)
Expand Down Expand Up @@ -1856,6 +1926,7 @@ class BrowserTabViewModelTest {
return nav
}

private fun privacyGradeState() = testee.privacyGradeViewState.value!!
private fun ctaViewState() = testee.ctaViewState.value!!
private fun browserViewState() = testee.browserViewState.value!!
private fun omnibarViewState() = testee.omnibarViewState.value!!
Expand Down
15 changes: 15 additions & 0 deletions app/src/androidTest/java/com/duckduckgo/app/cta/ui/CtaTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,21 @@ class CtaTest {
assertEquals("<b>Other, Facebook</b>withZero", value)
}

@Test
fun whenTrackersBlockedReturnOnlyTrackersWithDisplayName() {
val trackers = listOf(
TrackingEvent("facebook.com", "facebook.com", blocked = true, entity = TestEntity("Facebook", "Facebook", 3.0), categories = null),
TrackingEvent("other.com", "other.com", blocked = true, entity = TestEntity("Other", "", 9.0), categories = null)
)
val site = site(events = trackers)

val testee =
DaxDialogCta.DaxTrackersBlockedCta(mockOnboardingStore, mockAppInstallStore, site.orderedTrackingEntities(), "http://www.trackers.com")
val value = testee.getDaxText(mockActivity)

assertEquals("<b>Facebook</b>withZero", value)
}

@Test
fun whenMultipleTrackersFromSameNetworkBlockedReturnOnlyOneWithZeroString() {
val trackers = listOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class TdsEntityJsonTest {
fun whenFormatIsValidThenEntitiesAreCreated() {
val json = loadText("json/tds_entities.json")
val entities = jsonAdapter.fromJson(json)!!.jsonToEntities()
assertEquals(3, entities.count())
assertEquals(4, entities.count())
}

@Test
Expand All @@ -59,4 +59,12 @@ class TdsEntityJsonTest {
val entity = entities[2]
assertEquals("4Cite Marketing", entity.displayName)
}

@Test
fun whenEntityHasBlankDisplayNameThenDisplayNameIsSameAsName() {
val json = loadText("json/tds_entities.json")
val entities = jsonAdapter.fromJson(json)!!.jsonToEntities()
val entity = entities.last()
assertEquals("AT Internet", entity.displayName)
}
}
9 changes: 9 additions & 0 deletions app/src/androidTest/resources/json/tds_entities.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@
"securedvisit.com"
],
"prevalence": 0.341
},
"AT Internet": {
"domains": [
"xiti.com",
"aticdn.net",
"ati-host.net"
],
"displayName": "",
"prevalence": 0.718
}
}
}
82 changes: 54 additions & 28 deletions app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ import com.duckduckgo.app.global.ViewModelFactory
import com.duckduckgo.app.global.device.DeviceInfo
import com.duckduckgo.app.global.model.orderedTrackingEntities
import com.duckduckgo.app.global.view.*
import com.duckduckgo.app.privacy.model.PrivacyGrade
import com.duckduckgo.app.privacy.renderer.icon
import com.duckduckgo.app.statistics.VariantManager
import com.duckduckgo.app.statistics.pixels.Pixel
Expand Down Expand Up @@ -136,7 +135,7 @@ import javax.inject.Inject
import kotlin.concurrent.thread
import kotlin.coroutines.CoroutineContext

class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogListener {
class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogListener, TrackersAnimatorListener {

private val supervisorJob = SupervisorJob()

Expand Down Expand Up @@ -300,6 +299,8 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi

decorateWithFeatures()

animatorHelper.setListener(this)

if (savedInstanceState == null) {
viewModel.onViewReady()
messageFromPreviousTab?.let {
Expand Down Expand Up @@ -432,6 +433,10 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi
it.let { viewModel.onSurveyChanged(it) }
})

viewModel.privacyGradeViewState.observe(viewLifecycleOwner, Observer {
it.let { renderer.renderPrivacyGrade(it) }
})

addTabsObserver()
}

Expand Down Expand Up @@ -488,7 +493,7 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi

private fun processCommand(it: Command?) {
if (it !is Command.DaxCommand) {
renderer.cancelAllAnimations()
renderer.cancelTrackersAnimation()
}
when (it) {
is Command.Refresh -> refresh()
Expand Down Expand Up @@ -740,15 +745,6 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi
toolbar.privacyGradeButton.setOnClickListener {
browserActivity?.launchPrivacyDashboard()
}

viewModel.privacyGrade.observe(viewLifecycleOwner, Observer<PrivacyGrade> {
Timber.d("Observed grade: $it")
it?.let { privacyGrade ->
val drawable = context?.getDrawable(privacyGrade.icon()) ?: return@let
privacyGradeButton?.setImageDrawable(drawable)
privacyGradeButton?.isEnabled = privacyGrade != PrivacyGrade.UNKNOWN
}
})
}

private fun configureFindInPage() {
Expand Down Expand Up @@ -1044,6 +1040,7 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi
}

override fun onDestroy() {
animatorHelper.removeListener()
supervisorJob.cancel()
popupMenu.dismiss()
destroyWebView()
Expand Down Expand Up @@ -1183,7 +1180,7 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi
}

private fun finishTrackerAnimation() {
animatorHelper.finishTrackerAnimation(omnibarViews(), networksContainer)
animatorHelper.finishTrackerAnimation(omnibarViews(), animationContainer)
}

private fun showHideTipsDialog(cta: Cta) {
Expand Down Expand Up @@ -1220,7 +1217,11 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi
.show()
}

fun omnibarViews(): List<View> = listOf(clearTextButton, omnibarTextInput, privacyGradeButton, searchIcon)
fun omnibarViews(): List<View> = listOf(clearTextButton, omnibarTextInput, searchIcon)

override fun onAnimationFinished() {
viewModel.stopShowingEmptyGrade()
}

companion object {
private const val TAB_ID_ARG = "TAB_ID_ARG"
Expand Down Expand Up @@ -1495,6 +1496,40 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi
private var lastSeenGlobalViewState: GlobalLayoutViewState? = null
private var lastSeenAutoCompleteViewState: AutoCompleteViewState? = null
private var lastSeenCtaViewState: CtaViewState? = null
private var lastSeenPrivacyGradeViewState: PrivacyGradeViewState? = null

fun renderPrivacyGrade(viewState: PrivacyGradeViewState) {

renderIfChanged(viewState, lastSeenPrivacyGradeViewState) {

val oldGrade = lastSeenPrivacyGradeViewState?.privacyGrade
val oldShowEmptyGrade = lastSeenPrivacyGradeViewState?.showEmptyGrade
val grade = viewState.privacyGrade
val newShowEmptyGrade = viewState.showEmptyGrade

val canChangeGrade = (oldGrade != grade && !newShowEmptyGrade) || (oldGrade == grade && oldShowEmptyGrade != newShowEmptyGrade)
lastSeenPrivacyGradeViewState = viewState

if (canChangeGrade) {
context?.let {
val drawable = if (viewState.showEmptyGrade) {
ContextCompat.getDrawable(it, R.drawable.privacygrade_icon_loading)
} else {
ContextCompat.getDrawable(it, viewState.privacyGrade.icon())
}
privacyGradeButton?.setImageDrawable(drawable)
}
}

privacyGradeButton?.isEnabled = viewState.isEnabled

if (viewState.shouldAnimate) {
animatorHelper.startPulseAnimation(privacyGradeButton)
} else {
animatorHelper.stopPulseAnimation()
}
}
}

fun renderAutocomplete(viewState: AutoCompleteViewState) {
renderIfChanged(viewState, lastSeenAutoCompleteViewState) {
Expand All @@ -1514,7 +1549,7 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi
lastSeenOmnibarViewState = viewState

if (viewState.isEditing) {
cancelAllAnimations()
cancelTrackersAnimation()
}

if (shouldUpdateOmnibarTextInput(viewState, viewState.omnibarText)) {
Expand Down Expand Up @@ -1558,7 +1593,7 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi

if (viewState.privacyOn) {
if (lastSeenOmnibarViewState?.isEditing == true) {
cancelAllAnimations()
cancelTrackersAnimation()
}

if (viewState.progress == MAX_PROGRESS) {
Expand All @@ -1578,23 +1613,14 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi
val events = site?.orderedTrackingEntities()

activity?.let { activity ->
animatorHelper.startTrackersAnimation(
lastSeenCtaViewState?.cta,
activity,
networksContainer,
omnibarViews(),
events
)
animatorHelper.startTrackersAnimation(lastSeenCtaViewState?.cta, activity, animationContainer, omnibarViews(), events)
}
}
}
}

fun cancelAllAnimations() {
animatorHelper.cancelAnimations()
networksContainer.alpha = 0f
clearTextButton.alpha = 1f
omnibarTextInput.alpha = 1f
fun cancelTrackersAnimation() {
animatorHelper.cancelAnimations(omnibarViews(), animationContainer)
}

fun renderGlobalViewState(viewState: GlobalLayoutViewState) {
Expand Down
Loading