diff --git a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt index e661c35dc161..0aae74ea7221 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt @@ -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 @@ -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 @@ -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) @@ -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!! diff --git a/app/src/androidTest/java/com/duckduckgo/app/cta/ui/CtaTest.kt b/app/src/androidTest/java/com/duckduckgo/app/cta/ui/CtaTest.kt index 9d436795ef63..0c4c79f40ab0 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/cta/ui/CtaTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/cta/ui/CtaTest.kt @@ -309,6 +309,21 @@ class CtaTest { assertEquals("Other, FacebookwithZero", 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("FacebookwithZero", value) + } + @Test fun whenMultipleTrackersFromSameNetworkBlockedReturnOnlyOneWithZeroString() { val trackers = listOf( diff --git a/app/src/androidTest/java/com/duckduckgo/app/trackerdetection/api/TdsEntityJsonTest.kt b/app/src/androidTest/java/com/duckduckgo/app/trackerdetection/api/TdsEntityJsonTest.kt index 797c65f35075..2e676151f58b 100644 --- a/app/src/androidTest/java/com/duckduckgo/app/trackerdetection/api/TdsEntityJsonTest.kt +++ b/app/src/androidTest/java/com/duckduckgo/app/trackerdetection/api/TdsEntityJsonTest.kt @@ -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 @@ -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) + } } diff --git a/app/src/androidTest/resources/json/tds_entities.json b/app/src/androidTest/resources/json/tds_entities.json index 685003ceb1b1..de136a91964b 100644 --- a/app/src/androidTest/resources/json/tds_entities.json +++ b/app/src/androidTest/resources/json/tds_entities.json @@ -19,6 +19,15 @@ "securedvisit.com" ], "prevalence": 0.341 + }, + "AT Internet": { + "domains": [ + "xiti.com", + "aticdn.net", + "ati-host.net" + ], + "displayName": "", + "prevalence": 0.718 } } } \ No newline at end of file diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt index 938d0dac4598..ea0397f2095d 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt @@ -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 @@ -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() @@ -300,6 +299,8 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi decorateWithFeatures() + animatorHelper.setListener(this) + if (savedInstanceState == null) { viewModel.onViewReady() messageFromPreviousTab?.let { @@ -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() } @@ -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() @@ -740,15 +745,6 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi toolbar.privacyGradeButton.setOnClickListener { browserActivity?.launchPrivacyDashboard() } - - viewModel.privacyGrade.observe(viewLifecycleOwner, Observer { - 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() { @@ -1044,6 +1040,7 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi } override fun onDestroy() { + animatorHelper.removeListener() supervisorJob.cancel() popupMenu.dismiss() destroyWebView() @@ -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) { @@ -1220,7 +1217,11 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi .show() } - fun omnibarViews(): List = listOf(clearTextButton, omnibarTextInput, privacyGradeButton, searchIcon) + fun omnibarViews(): List = listOf(clearTextButton, omnibarTextInput, searchIcon) + + override fun onAnimationFinished() { + viewModel.stopShowingEmptyGrade() + } companion object { private const val TAB_ID_ARG = "TAB_ID_ARG" @@ -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) { @@ -1514,7 +1549,7 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi lastSeenOmnibarViewState = viewState if (viewState.isEditing) { - cancelAllAnimations() + cancelTrackersAnimation() } if (shouldUpdateOmnibarTextInput(viewState, viewState.omnibarText)) { @@ -1558,7 +1593,7 @@ class BrowserTabFragment : Fragment(), FindListener, CoroutineScope, DaxDialogLi if (viewState.privacyOn) { if (lastSeenOmnibarViewState?.isEditing == true) { - cancelAllAnimations() + cancelTrackersAnimation() } if (viewState.progress == MAX_PROGRESS) { @@ -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) { diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt index b56c2c7cc5de..99da3887dd55 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt @@ -164,6 +164,14 @@ class BrowserTabViewModel( val canFindInPage: Boolean = false ) + data class PrivacyGradeViewState( + val privacyGrade: PrivacyGrade? = null, + val shouldAnimate: Boolean = false, + val showEmptyGrade: Boolean = true + ) { + val isEnabled: Boolean = !showEmptyGrade && privacyGrade != PrivacyGrade.UNKNOWN + } + data class AutoCompleteViewState( val showSuggestions: Boolean = false, val searchResults: AutoCompleteResult = AutoCompleteResult("", emptyList()) @@ -221,11 +229,11 @@ class BrowserTabViewModel( val findInPageViewState: MutableLiveData = MutableLiveData() val ctaViewState: MutableLiveData = MutableLiveData() var siteLiveData: MutableLiveData = MutableLiveData() + val privacyGradeViewState: MutableLiveData = MutableLiveData() var skipHome = false val tabs: LiveData> = tabRepository.liveTabs val survey: LiveData = ctaViewModel.surveyLiveData - val privacyGrade: MutableLiveData = MutableLiveData() val command: SingleLiveEvent = SingleLiveEvent() val url: String? @@ -613,6 +621,9 @@ class BrowserTabViewModel( newProgress } loadingViewState.value = progress.copy(isLoading = isLoading, progress = visualProgress) + + val showLoadingGrade = progress.privacyOn || isLoading + privacyGradeViewState.value = currentPrivacyGradeState().copy(shouldAnimate = isLoading, showEmptyGrade = showLoadingGrade) } private fun registerSiteVisit() { @@ -680,7 +691,7 @@ class BrowserTabViewModel( withContext(dispatchers.main()) { siteLiveData.value = site - privacyGrade.value = improvedGrade + privacyGradeViewState.value = currentPrivacyGradeState().copy(privacyGrade = improvedGrade) } withContext(dispatchers.io()) { @@ -689,6 +700,12 @@ class BrowserTabViewModel( } } + fun stopShowingEmptyGrade() { + if (currentPrivacyGradeState().showEmptyGrade) { + privacyGradeViewState.value = currentPrivacyGradeState().copy(showEmptyGrade = false) + } + } + override fun showFileChooser(filePathCallback: ValueCallback>, fileChooserParams: WebChromeClient.FileChooserParams) { command.value = ShowFileChooser(filePathCallback, fileChooserParams) } @@ -700,6 +717,7 @@ class BrowserTabViewModel( private fun currentOmnibarViewState(): OmnibarViewState = omnibarViewState.value!! private fun currentLoadingViewState(): LoadingViewState = loadingViewState.value!! private fun currentCtaViewState(): CtaViewState = ctaViewState.value!! + private fun currentPrivacyGradeState(): PrivacyGradeViewState = privacyGradeViewState.value!! fun onOmnibarInputStateChanged(query: String, hasFocus: Boolean, hasQueryChanged: Boolean) { @@ -718,6 +736,11 @@ class BrowserTabViewModel( val showPrivacyGrade = !hasFocus val showSearchIcon = hasFocus + // show the real grade in case the animation was canceled before changing the state, this avoids showing an empty grade when regaining focus. + if (showPrivacyGrade) { + privacyGradeViewState.value = currentPrivacyGradeState().copy(showEmptyGrade = false) + } + omnibarViewState.value = currentOmnibarViewState.copy(isEditing = hasFocus) val currentBrowserViewState = currentBrowserViewState() @@ -925,6 +948,7 @@ class BrowserTabViewModel( omnibarViewState.value = OmnibarViewState() findInPageViewState.value = FindInPageViewState() ctaViewState.value = CtaViewState() + privacyGradeViewState.value = PrivacyGradeViewState() } fun onShareSelected() { diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTrackersAnimatorHelper.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTrackersAnimatorHelper.kt index dba431fa1b41..c332d9ecd581 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTrackersAnimatorHelper.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTrackersAnimatorHelper.kt @@ -18,31 +18,55 @@ package com.duckduckgo.app.browser import android.animation.AnimatorSet import android.animation.ObjectAnimator +import android.animation.PropertyValuesHolder import android.app.Activity +import android.content.Context import android.view.Gravity import android.view.View +import android.view.ViewGroup import android.widget.FrameLayout +import android.widget.ImageButton import android.widget.ImageView import androidx.appcompat.widget.AppCompatTextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import androidx.core.animation.addListener import androidx.core.view.children import androidx.core.widget.TextViewCompat import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import com.duckduckgo.app.cta.ui.Cta import com.duckduckgo.app.cta.ui.DaxDialogCta +import com.duckduckgo.app.global.view.toPx import com.duckduckgo.app.privacy.renderer.TrackersRenderer import com.duckduckgo.app.trackerdetection.model.Entity +interface TrackersAnimatorListener { + fun onAnimationFinished() +} + class BrowserTrackersAnimatorHelper { private var trackersAnimation: AnimatorSet = AnimatorSet() + private var pulseAnimation: AnimatorSet = AnimatorSet() + private var listener: TrackersAnimatorListener? = null - fun startTrackersAnimation(cta: Cta?, activity: Activity, container: ConstraintLayout, omnibarViews: List, entities: List?) { - if (entities.isNullOrEmpty()) return + fun startTrackersAnimation( + cta: Cta?, + activity: Activity, + container: ConstraintLayout, + omnibarViews: List, + entities: List? + ) { + if (entities.isNullOrEmpty()) { + listener?.onAnimationFinished() + return + } val logoViews: List = getLogosViewListInContainer(activity, container, entities) - if (logoViews.isEmpty()) return + if (logoViews.isEmpty()) { + listener?.onAnimationFinished() + return + } if (!trackersAnimation.isRunning) { trackersAnimation = if (cta is DaxDialogCta.DaxTrackersBlockedCta) { @@ -57,109 +81,180 @@ class BrowserTrackersAnimatorHelper { } } - fun cancelAnimations() { - if (trackersAnimation.isRunning) { - trackersAnimation.end() + fun startPulseAnimation(view: ImageButton) { + if (!pulseAnimation.isRunning) { + val scaleDown = ObjectAnimator.ofPropertyValuesHolder( + view, + PropertyValuesHolder.ofFloat("scaleX", 1f, 0.9f, 1f), + PropertyValuesHolder.ofFloat("scaleY", 1f, 0.9f, 1f) + ) + scaleDown.repeatCount = ObjectAnimator.INFINITE + scaleDown.duration = PULSE_ANIMATION_DURATION + + pulseAnimation = AnimatorSet().apply { + play(scaleDown) + start() + } } } - fun finishTrackerAnimation(fadeInViews: List, container: ConstraintLayout) { - val animateOmnibarIn = animateOmnibarIn(fadeInViews) - val animateLogosOut = animateFadeOut(container) + fun stopPulseAnimation() { + if (pulseAnimation.isRunning) { + pulseAnimation.end() + } + } + + fun removeListener() { + listener = null + } + + fun setListener(animatorListener: TrackersAnimatorListener) { + listener = animatorListener + } + + fun cancelAnimations(omnibarViews: List, container: ViewGroup) { + stopTrackersAnimation() + stopPulseAnimation() + listener?.onAnimationFinished() + omnibarViews.forEach { it.alpha = 1f } + container.alpha = 0f + } + + fun finishTrackerAnimation(omnibarViews: List, container: ViewGroup) { trackersAnimation = AnimatorSet().apply { - play(animateLogosOut).before(animateOmnibarIn) + play(animateLogosSlideOut(container.children.toList())) + .before(animateOmnibarIn(omnibarViews)) + .before(animateFadeOut(container)) start() + addListener(onEnd = { listener?.onAnimationFinished() }) } } - private fun getLogosViewListInContainer(activity: Activity, container: ConstraintLayout, entities: List): List { + private fun getLogosViewListInContainer(activity: Activity, container: ViewGroup, entities: List): List { container.removeAllViews() container.alpha = 0f - val logos = createResourcesIdList(activity, entities) + val logos = createTrackerLogoList(activity, entities) return createLogosViewList(activity, container, logos) } private fun createLogosViewList( activity: Activity, - container: ConstraintLayout, + container: ViewGroup, resourcesId: List ): List { return resourcesId.map { - return@map if (it.resId == R.drawable.other_tracker_bg) { - val frameLayout = createTrackerTextLogo(activity, it) - container.addView(frameLayout) - frameLayout - } else { - val imageView = createTrackerImageLogo(activity, it) - container.addView(imageView) - imageView + val frameLayout = when (it) { + is TrackerLogo.ImageLogo -> createTrackerImageLogo(activity, it) + is TrackerLogo.LetterLogo -> createTrackerTextLogo(activity, it) + is TrackerLogo.StackedLogo -> createTrackerStackedLogo(activity, it) } + container.addView(frameLayout) + return@map frameLayout } } - private fun createTrackerTextLogo(activity: Activity, trackerLogo: TrackerLogo): FrameLayout { + private fun getParams(): FrameLayout.LayoutParams { val params = FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT) - val frameLayout = FrameLayout(activity) + params.gravity = Gravity.CENTER + return params + } + + private fun createAnimatedDrawable(context: Context): AnimatedVectorDrawableCompat? { + return AnimatedVectorDrawableCompat.create(context, R.drawable.network_cross_anim) + } + + private fun createFrameLayoutContainer(context: Context): FrameLayout { + val frameLayout = FrameLayout(context) + frameLayout.alpha = 0f frameLayout.id = View.generateViewId() + frameLayout.layoutParams = getParams() + frameLayout.setBackgroundResource(R.drawable.background_tracker_logo) + return frameLayout + } + + private fun createImageView(context: Context, resId: Int): ImageView { + val imageView = ImageView(context) + imageView.scaleType = ImageView.ScaleType.CENTER_CROP + imageView.setBackgroundResource(resId) + imageView.id = View.generateViewId() + imageView.layoutParams = getParams() + return imageView + } + + private fun createTextView(context: Context): AppCompatTextView { + val textView = AppCompatTextView(context) + textView.gravity = Gravity.CENTER + TextViewCompat.setTextAppearance(textView, R.style.UnknownTrackerText) + textView.layoutParams = getParams() + + return textView + } + + private fun createTrackerTextLogo(activity: Activity, trackerLogo: TrackerLogo.LetterLogo): FrameLayout { + val animatedDrawable = createAnimatedDrawable(activity) val animationView = ImageView(activity) - val animatedDrawable = AnimatedVectorDrawableCompat.create(activity, R.drawable.network_cross_anim) animationView.setImageDrawable(animatedDrawable) - animationView.layoutParams = params + animationView.layoutParams = getParams() - val textView = AppCompatTextView(activity) - textView.gravity = Gravity.CENTER + val textView = createTextView(activity) textView.setBackgroundResource(trackerLogo.resId) - TextViewCompat.setTextAppearance(textView, R.style.UnknownTrackerText) textView.text = trackerLogo.trackerLetter - textView.layoutParams = params + val frameLayout = createFrameLayoutContainer(activity) frameLayout.addView(textView) frameLayout.addView(animationView) return frameLayout } - private fun createTrackerImageLogo(activity: Activity, trackerLogo: TrackerLogo): ImageView { - val imageView = ImageView(activity) - imageView.scaleType = ImageView.ScaleType.CENTER_CROP - val animatedDrawable = AnimatedVectorDrawableCompat.create(activity, R.drawable.network_cross_anim) + private fun createTrackerStackedLogo(activity: Activity, trackerLogo: TrackerLogo.StackedLogo): FrameLayout { + val imageView = createImageView(activity, trackerLogo.resId) + val frameLayout = createFrameLayoutContainer(activity) + + frameLayout.addView(imageView) + + return frameLayout + } + + private fun createTrackerImageLogo(activity: Activity, trackerLogo: TrackerLogo.ImageLogo): FrameLayout { + val imageView = createImageView(activity, trackerLogo.resId) + val animatedDrawable = createAnimatedDrawable(activity) imageView.setImageDrawable(animatedDrawable) - imageView.setBackgroundResource(trackerLogo.resId) - imageView.id = View.generateViewId() - return imageView + + val frameLayout = createFrameLayoutContainer(activity) + frameLayout.addView(imageView) + + return frameLayout } - private fun createResourcesIdList(activity: Activity, entities: List): List { + private fun createTrackerLogoList(activity: Activity, entities: List): List { if (activity.packageName == null) return emptyList() - val resourcesList = entities + val trackerLogoList = entities + .asSequence() .distinct() .take(MAX_LOGOS_SHOWN + 1) .map { - val res = TrackersRenderer().networkLogoIcon(activity, it.name) - if (res == null) { - TrackerLogo(R.drawable.other_tracker_bg, it.displayName.take(1)) + val resId = TrackersRenderer().networkLogoIcon(activity, it.name) + if (resId == null) { + TrackerLogo.LetterLogo(it.displayName.take(1)) } else { - TrackerLogo(res) + TrackerLogo.ImageLogo(resId) } } .toMutableList() - return if (resourcesList.size <= MAX_LOGOS_SHOWN) { - resourcesList + return if (trackerLogoList.size <= MAX_LOGOS_SHOWN) { + trackerLogoList } else { - resourcesList.take(MAX_LOGOS_SHOWN - 1) + trackerLogoList.take(MAX_LOGOS_SHOWN) .toMutableList() - .apply { add(TrackerLogo(R.drawable.ic_more_trackers)) } + .apply { add(TrackerLogo.StackedLogo()) } } } - private fun animateBlockedLogos(views: List) { + private fun animateLogosBlocked(views: List) { views.map { - if (it is ImageView) { - val animatedVectorDrawableCompat = it.drawable as? AnimatedVectorDrawableCompat - animatedVectorDrawableCompat?.start() - } if (it is FrameLayout) { val view: ImageView? = it.children.filter { child -> child is ImageView }.firstOrNull() as ImageView? view?.let { @@ -175,12 +270,21 @@ class BrowserTrackersAnimatorHelper { omnibarViews: List, logoViews: List ): AnimatorSet { - return AnimatorSet().apply { - play(createPartialTrackersAnimation(container, omnibarViews, logoViews)) - play(animateFadeOut(container)) + val finalAnimation = AnimatorSet().apply { + play(animateLogosSlideOut(logoViews)) .after(TRACKER_LOGOS_DELAY_ON_SCREEN) + .before(animateFadeOut(container)) .before(animateOmnibarIn(omnibarViews)) } + + return AnimatorSet().apply { + playSequentially( + createPartialTrackersAnimation(container, omnibarViews, logoViews), + finalAnimation + ) + start() + addListener(onEnd = { listener?.onAnimationFinished() }) + } } private fun createPartialTrackersAnimation( @@ -188,14 +292,12 @@ class BrowserTrackersAnimatorHelper { omnibarViews: List, logoViews: List ): AnimatorSet { - val fadeOmnibarOut = animateOmnibarOut(omnibarViews) - val fadeLogosIn = animateFadeIn(container) - applyConstraintSet(container, logoViews) - animateBlockedLogos(logoViews) + container.alpha = 1f + animateLogosBlocked(logoViews) return AnimatorSet().apply { - play(fadeLogosIn).after(fadeOmnibarOut) + play(animateLogosSlideIn(logoViews)).after(animateOmnibarOut(omnibarViews)) } } @@ -207,15 +309,21 @@ class BrowserTrackersAnimatorHelper { constraints.connect(view.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP) constraints.connect(view.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM) if (index == 0) { - constraints.connect(view.id, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START) + constraints.connect(view.id, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, FIRST_LOGO_MARGIN_IN_DP.toPx()) } else { - constraints.connect(view.id, ConstraintSet.START, views[index - 1].id, ConstraintSet.END, 10) + constraints.setTranslationX(view.id, (NORMAL_LOGO_MARGIN_IN_DP.toPx() * index)) + constraints.connect(view.id, ConstraintSet.START, views[index - 1].id, ConstraintSet.END, 0) } if (index == views.size - 1) { + if (views.size == MAX_LOGOS_SHOWN + 1) { + constraints.setTranslationX(view.id, (STACKED_LOGO_MARGIN_IN_DP.toPx() * index)) + } constraints.connect(view.id, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END) } } + views.reversed().map { it.bringToFront() } + val viewIds = views.map { it.id }.toIntArray() if (viewIds.size > 1) { @@ -233,6 +341,14 @@ class BrowserTrackersAnimatorHelper { constraints.applyTo(container) } + private fun calculateMarginInPx(position: Int): Int = START_MARGIN_IN_DP.toPx() + (position * LOGO_SIZE_IN_DP.toPx()) + + private fun stopTrackersAnimation() { + if (trackersAnimation.isRunning) { + trackersAnimation.end() + } + } + private fun animateOmnibarOut(views: List): AnimatorSet { val animators = views.map { animateFadeOut(it) @@ -251,12 +367,50 @@ class BrowserTrackersAnimatorHelper { } } - private fun animateFadeOut(view: View): ObjectAnimator { - return ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).apply { + private fun animateLogosSlideIn(views: List): AnimatorSet { + val initialMargin = (views.first().layoutParams as ConstraintLayout.LayoutParams).marginStart.toFloat() + val slideInAnimators = views.mapIndexed { index, it -> + val margin = calculateMarginInPx(index) + initialMargin + animateSlideIn(it, margin) + } + val fadeInAnimators = views.map { + animateFadeIn(it) + } + return AnimatorSet().apply { + playTogether(slideInAnimators + fadeInAnimators) + } + } + + private fun animateLogosSlideOut(views: List): AnimatorSet { + val slideOutAnimators = views.map { + animateSlideOut(it) + } + val fadeOutAnimators = views.map { + animateFadeOut(it) + } + return AnimatorSet().apply { + playTogether(slideOutAnimators + fadeOutAnimators) + } + } + + private fun animateSlideIn(view: View, margin: Float): ObjectAnimator { + return ObjectAnimator.ofFloat(view, "x", 0f, margin + view.translationX).apply { + duration = DEFAULT_ANIMATION_DURATION + } + } + + private fun animateSlideOut(view: View): ObjectAnimator { + return ObjectAnimator.ofFloat(view, "x", 0f).apply { duration = DEFAULT_ANIMATION_DURATION } } + private fun animateFadeOut(view: View, durationInMs: Long = DEFAULT_ANIMATION_DURATION): ObjectAnimator { + return ObjectAnimator.ofFloat(view, "alpha", 1f, 0f).apply { + duration = durationInMs + } + } + private fun animateFadeIn(view: View): ObjectAnimator { return ObjectAnimator.ofFloat(view, "alpha", 0f, 1f).apply { duration = DEFAULT_ANIMATION_DURATION @@ -266,8 +420,18 @@ class BrowserTrackersAnimatorHelper { companion object { private const val TRACKER_LOGOS_DELAY_ON_SCREEN = 2400L private const val DEFAULT_ANIMATION_DURATION = 150L + private const val PULSE_ANIMATION_DURATION = 1500L private const val MAX_LOGOS_SHOWN = 3 + private const val LOGO_SIZE_IN_DP = 26 + private const val START_MARGIN_IN_DP = 10 + private const val STACKED_LOGO_MARGIN_IN_DP = -11.5f + private const val NORMAL_LOGO_MARGIN_IN_DP = -7f + private const val FIRST_LOGO_MARGIN_IN_DP = 25 } } -data class TrackerLogo(val resId: Int, val trackerLetter: String = "") +sealed class TrackerLogo(val resId: Int) { + class ImageLogo(resId: Int) : TrackerLogo(resId) + class LetterLogo(val trackerLetter: String = "", resId: Int = R.drawable.other_tracker_bg) : TrackerLogo(resId) + class StackedLogo(resId: Int = R.drawable.other_tracker_bg) : TrackerLogo(resId) +} diff --git a/app/src/main/java/com/duckduckgo/app/global/model/Site.kt b/app/src/main/java/com/duckduckgo/app/global/model/Site.kt index 92ad55dac0dd..7f7ee6e63ed8 100644 --- a/app/src/main/java/com/duckduckgo/app/global/model/Site.kt +++ b/app/src/main/java/com/duckduckgo/app/global/model/Site.kt @@ -65,6 +65,7 @@ interface Site { fun Site.orderedTrackingEntities(): List = trackingEvents .mapNotNull { it.entity } + .filter { it.displayName.isNotBlank() } .sortedByDescending { it.prevalence } fun Site.domainMatchesUrl(matchingUrl: String): Boolean { diff --git a/app/src/main/java/com/duckduckgo/app/global/view/ViewExtension.kt b/app/src/main/java/com/duckduckgo/app/global/view/ViewExtension.kt index 61d792dabbf6..08f1ce66b4c8 100644 --- a/app/src/main/java/com/duckduckgo/app/global/view/ViewExtension.kt +++ b/app/src/main/java/com/duckduckgo/app/global/view/ViewExtension.kt @@ -80,3 +80,4 @@ fun View.hideKeyboard(): Boolean { fun Int.toDp(): Int = (this / Resources.getSystem().displayMetrics.density).toInt() fun Int.toPx(): Int = (this * Resources.getSystem().displayMetrics.density).toInt() +fun Float.toPx(): Float = (this * Resources.getSystem().displayMetrics.density) diff --git a/app/src/main/java/com/duckduckgo/app/privacy/ui/TrackerNetworksAdapter.kt b/app/src/main/java/com/duckduckgo/app/privacy/ui/TrackerNetworksAdapter.kt index c8c542f0c1c1..d72b3a56248e 100644 --- a/app/src/main/java/com/duckduckgo/app/privacy/ui/TrackerNetworksAdapter.kt +++ b/app/src/main/java/com/duckduckgo/app/privacy/ui/TrackerNetworksAdapter.kt @@ -103,7 +103,7 @@ class TrackerNetworksAdapter : RecyclerView.Adapter() { holder.icon.show() holder.unknownIcon.gone() } else { - val drawable = Theming.getThemedDrawable(holder.icon.context, R.drawable.other_tracker_bg, DuckDuckGoTheme.LIGHT) + val drawable = Theming.getThemedDrawable(holder.icon.context, R.drawable.other_tracker_privacy_dashboard_bg, DuckDuckGoTheme.LIGHT) holder.unknownIcon.text = viewElement.networkDisplayName.take(1) holder.unknownIcon.background = drawable holder.unknownIcon.show() diff --git a/app/src/main/java/com/duckduckgo/app/trackerdetection/api/TdsJson.kt b/app/src/main/java/com/duckduckgo/app/trackerdetection/api/TdsJson.kt index 4a2e12de6aba..d353e153cfaa 100644 --- a/app/src/main/java/com/duckduckgo/app/trackerdetection/api/TdsJson.kt +++ b/app/src/main/java/com/duckduckgo/app/trackerdetection/api/TdsJson.kt @@ -28,7 +28,7 @@ class TdsJson { fun jsonToEntities(): List { return entities.mapNotNull { (key, value) -> - TdsEntity(key, value.displayName ?: key, value.prevalence) + TdsEntity(key, value.displayName.takeIf { !it.isNullOrBlank() } ?: key, value.prevalence) } } diff --git a/app/src/main/res/drawable/background_tracker_logo.xml b/app/src/main/res/drawable/background_tracker_logo.xml new file mode 100644 index 000000000000..b3641d2f8edb --- /dev/null +++ b/app/src/main/res/drawable/background_tracker_logo.xml @@ -0,0 +1,24 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/network_cross_anim.xml b/app/src/main/res/drawable/network_cross_anim.xml index 4f7aa1b641fd..20875e707360 100644 --- a/app/src/main/res/drawable/network_cross_anim.xml +++ b/app/src/main/res/drawable/network_cross_anim.xml @@ -50,7 +50,7 @@ android:duration="20" android:interpolator="@android:interpolator/linear" android:propertyName="strokeWidth" - android:startOffset="800" + android:startOffset="@integer/crossing_animation_delay_ms" android:valueFrom="0" android:valueTo="2" android:valueType="floatType" /> @@ -58,7 +58,7 @@ android:duration="200" android:interpolator="@android:interpolator/linear" android:propertyName="pathData" - android:startOffset="800" + android:startOffset="@integer/crossing_animation_delay_ms" android:valueFrom="M 10 10 L 10 10" android:valueTo="M 2 2 L 20 20" android:valueType="pathType" /> @@ -66,7 +66,7 @@ android:duration="200" android:interpolator="@android:interpolator/linear" android:propertyName="strokeAlpha" - android:startOffset="800" + android:startOffset="@integer/crossing_animation_delay_ms" android:valueFrom="0" android:valueTo="1" android:valueType="floatType" /> @@ -80,7 +80,7 @@ android:duration="20" android:interpolator="@android:interpolator/linear" android:propertyName="strokeWidth" - android:startOffset="800" + android:startOffset="@integer/crossing_animation_delay_ms" android:valueFrom="0" android:valueTo="6" android:valueType="floatType" /> @@ -88,7 +88,7 @@ android:duration="200" android:interpolator="@android:interpolator/linear" android:propertyName="pathData" - android:startOffset="800" + android:startOffset="@integer/crossing_animation_delay_ms" android:valueFrom="M 10 10 L 10 10" android:valueTo="M 2 2 L 20 20" android:valueType="pathType" /> @@ -96,7 +96,7 @@ android:duration="200" android:interpolator="@android:interpolator/linear" android:propertyName="strokeAlpha" - android:startOffset="800" + android:startOffset="@integer/crossing_animation_delay_ms" android:valueFrom="0" android:valueTo="1" android:valueType="floatType" /> diff --git a/app/src/main/res/drawable/other_tracker_bg.xml b/app/src/main/res/drawable/other_tracker_bg.xml index feb8e3a29aa9..95bc254a0a89 100644 --- a/app/src/main/res/drawable/other_tracker_bg.xml +++ b/app/src/main/res/drawable/other_tracker_bg.xml @@ -17,7 +17,8 @@ + + android:height="26dp" + android:width="26dp" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/other_tracker_privacy_dashboard_bg.xml b/app/src/main/res/drawable/other_tracker_privacy_dashboard_bg.xml new file mode 100644 index 000000000000..4dfbb3d19c56 --- /dev/null +++ b/app/src/main/res/drawable/other_tracker_privacy_dashboard_bg.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/privacygrade_icon_a.xml b/app/src/main/res/drawable/privacygrade_icon_a.xml index effb7aa4582f..302817601a52 100644 --- a/app/src/main/res/drawable/privacygrade_icon_a.xml +++ b/app/src/main/res/drawable/privacygrade_icon_a.xml @@ -1,9 +1,28 @@ + + + android:width="22dp" + android:height="22dp" + android:viewportWidth="22" + android:viewportHeight="22"> + android:pathData="M11,22C4.925,22 0,17.075 0,11C0,4.925 4.925,0 11,0C17.075,0 22,4.925 22,11C22,17.075 17.075,22 11,22ZM15.758,15.4L12.224,6.229L9.776,6.229L6.242,15.4L8.456,15.4L9.034,13.846L12.966,13.846L13.544,15.4L15.757,15.4L15.758,15.4ZM12.43,12.128L9.57,12.128L11,8.18L12.43,12.127L12.43,12.128Z" + android:strokeWidth="1" + android:fillColor="?attr/toolbarIconColor" + android:fillType="evenOdd" + android:strokeColor="#00000000"/> diff --git a/app/src/main/res/drawable/privacygrade_icon_b.xml b/app/src/main/res/drawable/privacygrade_icon_b.xml index 816a91c1640d..fb3e65a62c19 100644 --- a/app/src/main/res/drawable/privacygrade_icon_b.xml +++ b/app/src/main/res/drawable/privacygrade_icon_b.xml @@ -1,9 +1,28 @@ + + + android:width="22dp" + android:height="22dp" + android:viewportWidth="22" + android:viewportHeight="22"> + android:pathData="M11,22C4.925,22 0,17.075 0,11C0,4.925 4.925,0 11,0C17.075,0 22,4.925 22,11C22,17.075 17.075,22 11,22ZM12.815,15.771C14.575,15.771 15.483,14.671 15.483,13.282C15.483,12.142 14.713,11.192 13.723,11.042C14.589,10.862 15.303,10.079 15.303,8.938C15.303,7.714 14.41,6.6 12.663,6.6L7.839,6.6L7.839,15.771L12.816,15.771L12.815,15.771ZM12.238,10.244L9.79,10.244L9.79,8.319L12.238,8.319C12.898,8.319 13.31,8.717 13.31,9.281C13.31,9.872 12.898,10.244 12.238,10.244ZM12.32,14.053L9.79,14.053L9.79,11.963L12.32,11.963C13.076,11.963 13.489,12.43 13.489,13.008C13.489,13.668 13.049,14.053 12.32,14.053Z" + android:strokeWidth="1" + android:fillColor="?attr/toolbarIconColor" + android:fillType="evenOdd" + android:strokeColor="#00000000"/> diff --git a/app/src/main/res/drawable/privacygrade_icon_b_plus.xml b/app/src/main/res/drawable/privacygrade_icon_b_plus.xml index c30b3fdb1258..96b7c2476268 100644 --- a/app/src/main/res/drawable/privacygrade_icon_b_plus.xml +++ b/app/src/main/res/drawable/privacygrade_icon_b_plus.xml @@ -1,5 +1,5 @@ + android:width="22dp" + android:height="22dp" + android:viewportWidth="22" + android:viewportHeight="22"> + android:pathData="M11,22C4.925,22 0,17.075 0,11C0,4.925 4.925,0 11,0C17.075,0 22,4.925 22,11C22,17.075 17.075,22 11,22ZM15.446,10.77L13.612,10.77L13.612,12.604L15.446,12.604L15.446,14.438L17.279,14.438L17.279,12.604L19.113,12.604L19.113,10.771L17.279,10.771L17.279,8.937L15.446,8.937L15.446,10.771L15.446,10.77ZM9.322,15.813C11.082,15.813 11.99,14.713 11.99,13.323C11.99,12.183 11.22,11.233 10.23,11.083C11.096,10.903 11.811,10.12 11.811,8.979C11.811,7.755 10.918,6.641 9.171,6.641L4.345,6.641L4.345,15.813L9.323,15.813L9.322,15.813ZM8.745,10.285L6.298,10.285L6.298,8.36L8.745,8.36C9.405,8.36 9.818,8.759 9.818,9.323C9.818,9.913 9.405,10.285 8.745,10.285L8.745,10.285ZM8.828,14.094L6.298,14.094L6.298,12.004L8.828,12.004C9.584,12.004 9.996,12.471 9.996,13.049C9.996,13.709 9.556,14.094 8.828,14.094Z" + android:strokeWidth="1" + android:fillColor="?attr/toolbarIconColor" + android:fillType="evenOdd" + android:strokeColor="#00000000"/> diff --git a/app/src/main/res/drawable/privacygrade_icon_c.xml b/app/src/main/res/drawable/privacygrade_icon_c.xml index 77e0e6bb098a..f172ba5c327c 100644 --- a/app/src/main/res/drawable/privacygrade_icon_c.xml +++ b/app/src/main/res/drawable/privacygrade_icon_c.xml @@ -1,9 +1,28 @@ + + + android:width="22dp" + android:height="22dp" + android:viewportWidth="22" + android:viewportHeight="22"> + android:pathData="M11,22C4.925,22 0,17.075 0,11C0,4.925 4.925,0 11,0C17.075,0 22,4.925 22,11C22,17.075 17.075,22 11,22ZM11.495,15.538C13.503,15.538 14.671,14.424 15.317,13.283L13.64,12.471C13.255,13.214 12.43,13.805 11.495,13.805C9.818,13.805 8.608,12.526 8.608,10.794C8.608,9.061 9.818,7.783 11.495,7.783C12.43,7.783 13.255,8.373 13.64,9.116L15.317,8.291C14.671,7.136 13.502,6.05 11.495,6.05C8.731,6.05 6.6,7.961 6.6,10.794C6.6,13.613 8.731,15.538 11.495,15.538L11.495,15.538Z" + android:strokeWidth="1" + android:fillColor="?attr/toolbarIconColor" + android:fillType="evenOdd" + android:strokeColor="#00000000"/> diff --git a/app/src/main/res/drawable/privacygrade_icon_c_plus.xml b/app/src/main/res/drawable/privacygrade_icon_c_plus.xml index f84e9ec8a82a..fa437a4f8faf 100644 --- a/app/src/main/res/drawable/privacygrade_icon_c_plus.xml +++ b/app/src/main/res/drawable/privacygrade_icon_c_plus.xml @@ -1,5 +1,5 @@ + android:width="22dp" + android:height="22dp" + android:viewportWidth="22" + android:viewportHeight="22"> + android:pathData="M15.446,10.358L13.613,10.358L13.613,12.192L15.446,12.192L15.446,14.025L17.279,14.025L17.279,12.192L19.113,12.192L19.113,10.358L17.279,10.358L17.279,8.525L15.446,8.525L15.446,10.358ZM11,22C4.925,22 0,17.075 0,11C0,4.925 4.925,0 11,0C17.075,0 22,4.925 22,11C22,17.075 17.075,22 11,22ZM8.8,15.565C10.807,15.565 11.976,14.451 12.622,13.31L10.945,12.499C10.56,13.241 9.735,13.832 8.8,13.832C7.122,13.832 5.912,12.554 5.912,10.822C5.912,9.089 7.122,7.81 8.8,7.81C9.735,7.81 10.56,8.401 10.945,9.144L12.623,8.319C11.976,7.164 10.807,6.077 8.8,6.077C6.036,6.077 3.905,7.989 3.905,10.821C3.905,13.64 6.036,15.565 8.8,15.565Z" + android:strokeWidth="1" + android:fillColor="?attr/toolbarIconColor" + android:fillType="evenOdd" + android:strokeColor="#00000000"/> diff --git a/app/src/main/res/drawable/privacygrade_icon_d.xml b/app/src/main/res/drawable/privacygrade_icon_d.xml index 64fd2f12f441..6f95bf96e628 100644 --- a/app/src/main/res/drawable/privacygrade_icon_d.xml +++ b/app/src/main/res/drawable/privacygrade_icon_d.xml @@ -1,9 +1,28 @@ + + + android:width="22dp" + android:height="22dp" + android:viewportWidth="22" + android:viewportHeight="22"> + android:pathData="M11,22C4.925,22 0,17.075 0,11C0,4.925 4.925,0 11,0C17.075,0 22,4.925 22,11C22,17.075 17.075,22 11,22ZM11.33,15.771C14.19,15.771 16.184,13.956 16.184,11.179C16.184,8.429 14.19,6.6 11.316,6.6L7.7,6.6L7.7,15.771L11.33,15.771ZM11.316,14.052L9.652,14.052L9.652,8.32L11.33,8.32C13.2,8.32 14.19,9.571 14.19,11.18C14.19,12.747 13.131,14.053 11.316,14.053L11.316,14.052Z" + android:strokeWidth="1" + android:fillColor="?attr/toolbarIconColor" + android:fillType="evenOdd" + android:strokeColor="#00000000"/> diff --git a/app/src/main/res/drawable/privacygrade_icon_loading.xml b/app/src/main/res/drawable/privacygrade_icon_loading.xml new file mode 100644 index 000000000000..4a492d2d6909 --- /dev/null +++ b/app/src/main/res/drawable/privacygrade_icon_loading.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/layout/include_omnibar_toolbar.xml b/app/src/main/res/layout/include_omnibar_toolbar.xml index 57f533edfe67..f637edec915b 100644 --- a/app/src/main/res/layout/include_omnibar_toolbar.xml +++ b/app/src/main/res/layout/include_omnibar_toolbar.xml @@ -58,11 +58,16 @@ android:paddingEnd="6dp"> + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="@id/omnibarIconContainer" + app:layout_constraintStart_toStartOf="@id/omnibarIconContainer" + app:layout_constraintTop_toTopOf="@id/omnibarIconContainer" + + tools:ignore="RtlSymmetry" > + + android:src="@drawable/privacygrade_icon_loading" /> + android:src="@drawable/ic_loupe_24dp" /> diff --git a/app/src/main/res/layout/item_tracker_network_header.xml b/app/src/main/res/layout/item_tracker_network_header.xml index 04b62d2c2a9f..1eadea74a064 100644 --- a/app/src/main/res/layout/item_tracker_network_header.xml +++ b/app/src/main/res/layout/item_tracker_network_header.xml @@ -57,7 +57,7 @@ android:id="@+id/unknownIcon" android:layout_width="24dp" android:layout_height="24dp" - android:background="@drawable/other_tracker_bg" + android:background="@drawable/other_tracker_privacy_dashboard_bg" android:gravity="center" android:importantForAccessibility="no" android:textAppearance="@style/UnknownTrackerText" diff --git a/app/src/main/res/values-v23/animation-settings.xml b/app/src/main/res/values-v23/animation-settings.xml new file mode 100644 index 000000000000..6a64bcdace9d --- /dev/null +++ b/app/src/main/res/values-v23/animation-settings.xml @@ -0,0 +1,20 @@ + + + + + 1000 + \ No newline at end of file diff --git a/app/src/main/res/values/animation-settings.xml b/app/src/main/res/values/animation-settings.xml index ae09a720c2f7..3f06f5f4e23e 100644 --- a/app/src/main/res/values/animation-settings.xml +++ b/app/src/main/res/values/animation-settings.xml @@ -18,4 +18,5 @@ 300 300 + 500 \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 02035dad9e79..d00777c48ae7 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -303,7 +303,7 @@ center bold ?attr/omnibarLogoLetterTextColor - 12sp + 13sp