From 4485b7f6475218149fdd0eb3f4a676849005f513 Mon Sep 17 00:00:00 2001 From: Emily Kager Date: Tue, 10 Sep 2019 13:29:21 -0700 Subject: [PATCH] Add Tracking Protection Info Panel --- app/build.gradle | 2 + .../org/mozilla/fenix/BrowserDirection.kt | 3 +- .../java/org/mozilla/fenix/FeatureFlags.kt | 5 + .../java/org/mozilla/fenix/HomeActivity.kt | 5 + .../fenix/browser/BaseBrowserFragment.kt | 13 + .../mozilla/fenix/browser/BrowserFragment.kt | 86 +- .../java/org/mozilla/fenix/components/Core.kt | 9 +- .../components/toolbar/ToolbarIntegration.kt | 23 + .../fenix/customtabs/CustomTabsIntegration.kt | 24 + .../customtabs/ExternalAppBrowserFragment.kt | 12 + .../settings/RadioButtonInfoPreference.kt | 73 + .../fenix/settings/RadioButtonPreference.kt | 68 +- .../settings/TrackingProtectionFragment.kt | 77 +- .../TrackingProtectionSettingView.kt | 14 +- .../SwitchWithDescription.kt | 51 + .../TrackingProtectionBlockingFragment.kt | 49 + .../TrackingProtectionCategoryItem.kt | 48 + .../TrackingProtectionPanelDialogFragment.kt | 216 +++ .../TrackingProtectionPanelInteractor.kt | 31 + .../TrackingProtectionPanelView.kt | 265 +++ .../TrackingProtectionStore.kt | 129 ++ .../java/org/mozilla/fenix/utils/Settings.kt | 42 +- .../main/res/drawable/ic_arrowhead_right.xml | 12 + app/src/main/res/drawable/ic_cookies.xml | 13 + app/src/main/res/drawable/ic_cryptominers.xml | 14 + app/src/main/res/drawable/ic_etp_artwork.xml | 163 ++ .../main/res/drawable/ic_fingerprinters.xml | 13 + app/src/main/res/drawable/ic_info.xml | 6 +- .../res/drawable/ic_social_media_trackers.xml | 13 + .../main/res/drawable/ic_tracking_content.xml | 14 + .../ic_tracking_protection_disabled.xml | 28 +- app/src/main/res/drawable/ic_triangle.xml | 20 + .../drawable/onboarding_popup_background.xml | 19 + .../component_tracking_protection_panel.xml | 259 +++ .../res/layout/exceptions_description.xml | 2 +- .../fragment_quick_settings_dialog_sheet.xml | 129 +- .../layout/fragment_tracking_protection.xml | 10 + .../fragment_tracking_protection_blocking.xml | 80 + .../layout/preference_widget_radiobutton.xml | 2 +- ...reference_widget_radiobutton_with_info.xml | 76 + .../res/layout/switch_with_description.xml | 53 + .../layout/tracking_protection_category.xml | 57 + ...cking_protection_learn_more_preference.xml | 84 + .../tracking_protection_onboarding_popup.xml | 57 + app/src/main/res/navigation/nav_graph.xml | 77 +- app/src/main/res/raw-night/shield_json.json | 1582 +++++++++++++++++ app/src/main/res/raw/shield_json.json | 1582 +++++++++++++++++ app/src/main/res/raw/shield_json_dark.json | 1582 +++++++++++++++++ app/src/main/res/values/attrs.xml | 13 + app/src/main/res/values/dimens.xml | 4 + app/src/main/res/values/preference_keys.xml | 5 +- app/src/main/res/values/styles.xml | 3 + app/src/main/res/xml/preferences.xml | 2 +- .../xml/tracking_protection_preferences.xml | 27 +- .../TrackingProtectionPanelInteractorTest.kt | 63 + .../TrackingProtectionStoreTest.kt | 160 ++ buildSrc/src/main/java/Dependencies.kt | 5 + 57 files changed, 7331 insertions(+), 143 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/settings/RadioButtonInfoPreference.kt create mode 100644 app/src/main/java/org/mozilla/fenix/trackingprotection/SwitchWithDescription.kt create mode 100644 app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionBlockingFragment.kt create mode 100644 app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionCategoryItem.kt create mode 100644 app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelDialogFragment.kt create mode 100644 app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelInteractor.kt create mode 100644 app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelView.kt create mode 100644 app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStore.kt create mode 100644 app/src/main/res/drawable/ic_arrowhead_right.xml create mode 100644 app/src/main/res/drawable/ic_cookies.xml create mode 100644 app/src/main/res/drawable/ic_cryptominers.xml create mode 100644 app/src/main/res/drawable/ic_etp_artwork.xml create mode 100644 app/src/main/res/drawable/ic_fingerprinters.xml create mode 100644 app/src/main/res/drawable/ic_social_media_trackers.xml create mode 100644 app/src/main/res/drawable/ic_tracking_content.xml create mode 100644 app/src/main/res/drawable/ic_triangle.xml create mode 100644 app/src/main/res/drawable/onboarding_popup_background.xml create mode 100644 app/src/main/res/layout/component_tracking_protection_panel.xml create mode 100644 app/src/main/res/layout/fragment_tracking_protection.xml create mode 100644 app/src/main/res/layout/fragment_tracking_protection_blocking.xml create mode 100644 app/src/main/res/layout/preference_widget_radiobutton_with_info.xml create mode 100644 app/src/main/res/layout/switch_with_description.xml create mode 100644 app/src/main/res/layout/tracking_protection_category.xml create mode 100644 app/src/main/res/layout/tracking_protection_learn_more_preference.xml create mode 100644 app/src/main/res/layout/tracking_protection_onboarding_popup.xml create mode 100644 app/src/main/res/raw-night/shield_json.json create mode 100644 app/src/main/res/raw/shield_json.json create mode 100644 app/src/main/res/raw/shield_json_dark.json create mode 100644 app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelInteractorTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStoreTest.kt diff --git a/app/build.gradle b/app/build.gradle index 9e5b278a0..4f56c685f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -425,6 +425,8 @@ dependencies { implementation Deps.autodispose + implementation Deps.lottie + implementation Deps.adjust implementation Deps.installreferrer // Required by Adjust diff --git a/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt b/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt index c81d53f92..efa5c6fc7 100644 --- a/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt +++ b/app/src/main/java/org/mozilla/fenix/BrowserDirection.kt @@ -21,5 +21,6 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) { FromBookmarks(R.id.bookmarkFragment), FromHistory(R.id.historyFragment), FromExceptions(R.id.exceptionsFragment), - FromAbout(R.id.aboutFragment) + FromAbout(R.id.aboutFragment), + FromTrackingProtection(R.id.trackingProtectionFragment) } diff --git a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt index e3465a4d8..efb942db1 100644 --- a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt +++ b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt @@ -37,6 +37,11 @@ object FeatureFlags { */ const val mediaIntegration = true + /** + * Displays the categories blocked by ETP in a panel in the toolbar + */ + val etpCategories = nightly or debug + /** * Granular data deletion provides additional choices on the Delete Browsing Data * setting screen for cookies, cached images and files, and site permissions. diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 4cfba19d6..385871f4c 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -56,6 +56,7 @@ import org.mozilla.fenix.library.history.HistoryFragmentDirections import org.mozilla.fenix.search.SearchFragmentDirections import org.mozilla.fenix.settings.AboutFragmentDirections import org.mozilla.fenix.settings.SettingsFragmentDirections +import org.mozilla.fenix.settings.TrackingProtectionFragmentDirections import org.mozilla.fenix.share.ShareFragment import org.mozilla.fenix.theme.DefaultThemeManager import org.mozilla.fenix.theme.ThemeManager @@ -236,6 +237,10 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback ) BrowserDirection.FromAbout -> AboutFragmentDirections.actionAboutFragmentToBrowserFragment(customTabSessionId) + BrowserDirection.FromTrackingProtection -> + TrackingProtectionFragmentDirections.actionTrackingProtectionFragmentToBrowserFragment( + customTabSessionId + ) } private fun load( diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index 951ab3a6d..064a67a96 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -207,6 +207,10 @@ abstract class BaseBrowserFragment : Fragment(), BackHandler, SessionManager.Obs showQuickSettingsDialog() } + browserToolbarView.view.setOnTrackingProtectionClickedListener { + showTrackingProtectionPanel() + } + contextMenuFeature.set( feature = ContextMenuFeature( requireFragmentManager(), @@ -487,6 +491,8 @@ abstract class BaseBrowserFragment : Fragment(), BackHandler, SessionManager.Obs protected abstract fun navToQuickSettingsSheet(session: Session, sitePermissions: SitePermissions?) + protected abstract fun navToTrackingProtectionPanel(session: Session) + /** * Returns the top and bottom margins. */ @@ -530,6 +536,13 @@ abstract class BaseBrowserFragment : Fragment(), BackHandler, SessionManager.Obs } } + private fun showTrackingProtectionPanel() { + val session = getSessionById() ?: return + view?.let { + navToTrackingProtectionPanel(session) + } + } + /** * Returns the current session. */ diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index b55e8ca3e..dbce7e1c4 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -4,12 +4,17 @@ package org.mozilla.fenix.browser +import android.graphics.Color +import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.PopupWindow import android.widget.RadioButton import androidx.core.content.ContextCompat import androidx.lifecycle.Observer @@ -18,6 +23,7 @@ import androidx.navigation.fragment.findNavController import androidx.transition.TransitionInflater import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.fragment_browser.view.* +import kotlinx.android.synthetic.main.tracking_protection_onboarding_popup.view.* import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -31,6 +37,8 @@ import mozilla.components.feature.sitepermissions.SitePermissions import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.base.feature.ViewBoundFeatureWrapper +import mozilla.components.support.ktx.android.util.dpToPx +import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.browser.readermode.DefaultReaderModeController @@ -42,8 +50,10 @@ import org.mozilla.fenix.components.toolbar.BrowserToolbarController import org.mozilla.fenix.components.toolbar.BrowserToolbarViewInteractor import org.mozilla.fenix.components.toolbar.QuickActionSheetAction import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.increaseTapArea import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.requireComponents +import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.sessioncontrol.SessionControlChange import org.mozilla.fenix.home.sessioncontrol.TabCollection import org.mozilla.fenix.mvi.getManagedEmitter @@ -139,6 +149,18 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler { ).also { observer -> getSessionById()?.register(observer, this, autoPause = true) } + getSessionById()?.register(toolbarSessionObserver, this, autoPause = true) + } + + private val toolbarSessionObserver = object : Session.Observer { + override fun onLoadingStateChanged(session: Session, loading: Boolean) { + if (!loading && + context!!.settings.shouldShowTrackingProtectionOnboarding && + session.trackerBlockingEnabled + ) { + showTrackingProtectionOnboarding() + } + } } override fun onResume() { @@ -181,14 +203,26 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler { } override fun navToQuickSettingsSheet(session: Session, sitePermissions: SitePermissions?) { - val directions = BrowserFragmentDirections.actionBrowserFragmentToQuickSettingsSheetDialogFragment( - sessionId = session.id, - url = session.url, - isSecured = session.securityInfo.secure, - isTrackingProtectionOn = session.trackerBlockingEnabled, - sitePermissions = sitePermissions, - gravity = getAppropriateLayoutGravity() - ) + val directions = + BrowserFragmentDirections.actionBrowserFragmentToQuickSettingsSheetDialogFragment( + sessionId = session.id, + url = session.url, + isSecured = session.securityInfo.secure, + isTrackingProtectionOn = session.trackerBlockingEnabled, + sitePermissions = sitePermissions, + gravity = getAppropriateLayoutGravity() + ) + nav(R.id.browserFragment, directions) + } + + override fun navToTrackingProtectionPanel(session: Session) { + val directions = + BrowserFragmentDirections.actionBrowserFragmentToTrackingProtectionPanelDialogFragment( + sessionId = session.id, + url = session.url, + trackingProtectionEnabled = session.trackerBlockingEnabled, + gravity = getAppropriateLayoutGravity() + ) nav(R.id.browserFragment, directions) } @@ -309,8 +343,44 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler { } } + private fun showTrackingProtectionOnboarding() { + if (!FeatureFlags.etpCategories) { + return + } + context?.let { + it.settings.incrementTrackingProtectionOnboardingCount() + val layout = LayoutInflater.from(it) + .inflate(R.layout.tracking_protection_onboarding_popup, null) + layout.onboarding_message.text = + it.getString(R.string.etp_onboarding_message, getString(R.string.app_name)) + val trackingOnboarding = + PopupWindow( + layout, + TP_ONBOARDING_WIDTH.dpToPx(resources.displayMetrics), + LinearLayout.LayoutParams.WRAP_CONTENT, + true + ) + val closeButton = layout.findViewById(R.id.close_onboarding) + closeButton.increaseTapArea(BUTTON_INCREASE_DPS) + closeButton.setOnClickListener { + trackingOnboarding.dismiss() + } + trackingOnboarding.isOutsideTouchable = true + trackingOnboarding.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + trackingOnboarding.showAtLocation( + browserToolbarView.view, + Gravity.BOTTOM or Gravity.START, + TP_ONBOARDING_X_OFFSET.dpToPx(resources.displayMetrics), + browserToolbarView.view.height + ) + } + } + companion object { + private const val BUTTON_INCREASE_DPS = 12 + private const val TP_ONBOARDING_X_OFFSET = 4 private const val SHARED_TRANSITION_MS = 200L + private const val TP_ONBOARDING_WIDTH = 256 private const val TAB_ITEM_TRANSITION_NAME = "tab_item" const val REPORT_SITE_ISSUE_URL = "https://webcompat.com/issues/new?url=%s&label=browser-fenix" diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index 4ef757907..ab2f8352e 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -98,7 +98,10 @@ class Core(private val context: Context) { withContext(Dispatchers.IO) { sessionStorage.restore() }?.let { snapshot -> - sessionManager.restore(snapshot, updateSelection = (sessionManager.selectedSession == null)) + sessionManager.restore( + snapshot, + updateSelection = (sessionManager.selectedSession == null) + ) } // Now that we have restored our previous state (if there's one) let's setup auto saving the state while @@ -151,7 +154,9 @@ class Core(private val context: Context) { normalMode: Boolean = context.settings.shouldUseTrackingProtection, privateMode: Boolean = true ): TrackingProtectionPolicy { - val trackingProtectionPolicy = TrackingProtectionPolicy.recommended() + val trackingProtectionPolicy = + if (context.settings.useStrictTrackingProtection) TrackingProtectionPolicy.strict() else + TrackingProtectionPolicy.recommended() return when { normalMode && privateMode -> trackingProtectionPolicy diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt index e3549da94..c4ff4cc88 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarIntegration.kt @@ -8,6 +8,8 @@ import android.content.Context import android.view.ViewGroup import androidx.navigation.NavOptions import androidx.navigation.Navigation +import com.airbnb.lottie.LottieCompositionFactory +import com.airbnb.lottie.LottieDrawable import androidx.navigation.fragment.FragmentNavigator import mozilla.components.browser.domains.autocomplete.DomainAutocompleteProvider import mozilla.components.browser.session.SessionManager @@ -20,6 +22,7 @@ import mozilla.components.feature.toolbar.ToolbarPresenter import mozilla.components.lib.publicsuffixlist.PublicSuffixList import mozilla.components.support.base.feature.LifecycleAwareFeature import mozilla.components.support.ktx.android.view.hideKeyboard +import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.R import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.nav @@ -53,6 +56,26 @@ class ToolbarIntegration( return@run } + val task = LottieCompositionFactory + .fromRawRes( + context, + ThemeManager.resolveAttribute(R.attr.shieldLottieFile, context) + ) + task.addListener { result -> + val lottieDrawable = LottieDrawable() + lottieDrawable.composition = result + toolbar.displayTrackingProtectionIcon = + context.settings.shouldUseTrackingProtection && FeatureFlags.etpCategories + toolbar.displaySeparatorView = + context.settings.shouldUseTrackingProtection && FeatureFlags.etpCategories + + toolbar.setTrackingProtectionIcons( + iconOnNoTrackersBlocked = context.getDrawable(R.drawable.ic_tracking_protection_enabled)!!, + iconOnTrackersBlocked = lottieDrawable, + iconDisabledForSite = context.getDrawable(R.drawable.ic_tracking_protection_disabled)!! + ) + } + val tabsAction = TabCounterToolbarButton( sessionManager, { diff --git a/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabsIntegration.kt b/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabsIntegration.kt index a9c77ddc4..29e1d76f5 100644 --- a/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabsIntegration.kt +++ b/app/src/main/java/org/mozilla/fenix/customtabs/CustomTabsIntegration.kt @@ -10,13 +10,18 @@ import android.view.Gravity import android.view.View import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.widget.NestedScrollView +import com.airbnb.lottie.LottieCompositionFactory +import com.airbnb.lottie.LottieDrawable import mozilla.components.browser.session.SessionManager import mozilla.components.browser.toolbar.BrowserToolbar import mozilla.components.feature.customtabs.CustomTabsToolbarFeature import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.base.feature.LifecycleAwareFeature +import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.R import org.mozilla.fenix.components.toolbar.ToolbarMenu +import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.theme.ThemeManager class CustomTabsIntegration( context: Context, @@ -50,6 +55,25 @@ class CustomTabsIntegration( // Hide the Quick Action Bar. quickActionbar.visibility = View.GONE + + val task = LottieCompositionFactory + .fromRawRes( + context, + ThemeManager.resolveAttribute(R.attr.shieldLottieFile, context) + ) + task.addListener { result -> + val lottieDrawable = LottieDrawable() + lottieDrawable.composition = result + toolbar.displayTrackingProtectionIcon = + context.settings.shouldUseTrackingProtection && FeatureFlags.etpCategories + toolbar.displaySeparatorView = false + + toolbar.setTrackingProtectionIcons( + iconOnNoTrackersBlocked = context.getDrawable(R.drawable.ic_tracking_protection_enabled)!!, + iconOnTrackersBlocked = lottieDrawable, + iconDisabledForSite = context.getDrawable(R.drawable.ic_tracking_protection_disabled)!! + ) + } } private val customTabToolbarMenu by lazy { diff --git a/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserFragment.kt index b20c42139..6b4eb4487 100644 --- a/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserFragment.kt @@ -78,6 +78,18 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), BackHandler { nav(R.id.externalAppBrowserFragment, directions) } + override fun navToTrackingProtectionPanel(session: Session) { + val directions = + ExternalAppBrowserFragmentDirections + .actionExternalAppBrowserFragmentToTrackingProtectionPanelDialogFragment( + sessionId = session.id, + url = session.url, + trackingProtectionEnabled = session.trackerBlockingEnabled, + gravity = getAppropriateLayoutGravity() + ) + nav(R.id.externalAppBrowserFragment, directions) + } + override fun getEngineMargins(): Pair { val toolbarSize = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height) return toolbarSize to 0 diff --git a/app/src/main/java/org/mozilla/fenix/settings/RadioButtonInfoPreference.kt b/app/src/main/java/org/mozilla/fenix/settings/RadioButtonInfoPreference.kt new file mode 100644 index 000000000..cd3a5ac33 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/RadioButtonInfoPreference.kt @@ -0,0 +1,73 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.settings + +import android.content.Context +import android.util.AttributeSet +import android.widget.ImageView +import androidx.core.content.res.TypedArrayUtils +import androidx.core.content.withStyledAttributes +import androidx.preference.PreferenceViewHolder +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.increaseTapArea + +class RadioButtonInfoPreference @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : RadioButtonPreference(context, attrs) { + private var infoClickListener: (() -> Unit)? = null + private var infoView: ImageView? = null + + fun onInfoClickListener(listener: (() -> Unit)) { + infoClickListener = listener + } + + override fun setEnabled(enabled: Boolean) { + super.setEnabled(enabled) + infoView?.alpha = if (enabled) FULL_ALPHA else HALF_ALPHA + infoView?.isEnabled = enabled + } + + init { + layoutResource = R.layout.preference_widget_radiobutton_with_info + + context.withStyledAttributes( + attrs, + androidx.preference.R.styleable.Preference, + TypedArrayUtils.getAttr( + context, + androidx.preference.R.attr.preferenceStyle, + android.R.attr.preferenceStyle + ), + 0 + ) { + val defaultValue = when { + hasValue(androidx.preference.R.styleable.Preference_defaultValue) -> + getBoolean(androidx.preference.R.styleable.Preference_defaultValue, false) + hasValue(androidx.preference.R.styleable.Preference_android_defaultValue) -> + getBoolean( + androidx.preference.R.styleable.Preference_android_defaultValue, + false + ) + else -> false + } + setDefaultValue(defaultValue) + } + } + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + infoView = holder.findViewById(R.id.info_button) as ImageView + infoView?.increaseTapArea(EXTRA_TAP_AREA) + infoView?.setOnClickListener { + infoClickListener?.invoke() + } + infoView?.alpha = if (isEnabled) FULL_ALPHA else HALF_ALPHA + } + + companion object { + const val EXTRA_TAP_AREA = 22 + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/RadioButtonPreference.kt b/app/src/main/java/org/mozilla/fenix/settings/RadioButtonPreference.kt index 34580319d..1b50f74cb 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/RadioButtonPreference.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/RadioButtonPreference.kt @@ -17,13 +17,14 @@ import androidx.preference.PreferenceViewHolder import org.mozilla.fenix.R import org.mozilla.fenix.ext.settings -class RadioButtonPreference @JvmOverloads constructor( +open class RadioButtonPreference @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : Preference(context, attrs) { private val radioGroups = mutableListOf() - private lateinit var summaryView: TextView - private lateinit var radioButton: RadioButton + private var summaryView: TextView? = null + private var titleView: TextView? = null + private var radioButton: RadioButton? = null private var shouldSummaryBeParsedAsHtmlContent: Boolean = true private var defaultValue: Boolean = false private var clickListener: (() -> Unit)? = null @@ -34,14 +35,21 @@ class RadioButtonPreference @JvmOverloads constructor( context.withStyledAttributes( attrs, androidx.preference.R.styleable.Preference, - getAttr(context, androidx.preference.R.attr.preferenceStyle, android.R.attr.preferenceStyle), + getAttr( + context, + androidx.preference.R.attr.preferenceStyle, + android.R.attr.preferenceStyle + ), 0 ) { defaultValue = when { hasValue(androidx.preference.R.styleable.Preference_defaultValue) -> getBoolean(androidx.preference.R.styleable.Preference_defaultValue, false) hasValue(androidx.preference.R.styleable.Preference_android_defaultValue) -> - getBoolean(androidx.preference.R.styleable.Preference_android_defaultValue, false) + getBoolean( + androidx.preference.R.styleable.Preference_android_defaultValue, + false + ) else -> false } } @@ -55,6 +63,17 @@ class RadioButtonPreference @JvmOverloads constructor( clickListener = listener } + override fun setEnabled(enabled: Boolean) { + super.setEnabled(enabled) + if (!enabled) { + summaryView?.alpha = HALF_ALPHA + titleView?.alpha = HALF_ALPHA + } else { + summaryView?.alpha = FULL_ALPHA + titleView?.alpha = FULL_ALPHA + } + } + override fun onBindViewHolder(holder: PreferenceViewHolder) { super.onBindViewHolder(holder) @@ -75,44 +94,53 @@ class RadioButtonPreference @JvmOverloads constructor( private fun updateRadioValue(isChecked: Boolean) { persistBoolean(isChecked) - radioButton.isChecked = isChecked + radioButton?.isChecked = isChecked context.settings.preferences.edit().putBoolean(key, isChecked) .apply() } private fun bindRadioButton(holder: PreferenceViewHolder) { radioButton = holder.findViewById(R.id.radio_button) as RadioButton - radioButton.isChecked = context.settings.preferences.getBoolean(key, false) - radioButton.setStartCheckedIndicator() + radioButton?.isChecked = context.settings.preferences.getBoolean(key, defaultValue) + radioButton?.setStartCheckedIndicator() } private fun toggleRadioGroups() { - if (radioButton.isChecked) { + if (radioButton?.isChecked == true) { radioGroups.forEach { it.updateRadioValue(false) } } } private fun bindTitle(holder: PreferenceViewHolder) { - val titleView = holder.findViewById(R.id.title) as TextView + titleView = holder.findViewById(R.id.title) as TextView + titleView?.alpha = if (isEnabled) FULL_ALPHA else HALF_ALPHA if (!title.isNullOrEmpty()) { - titleView.text = title + titleView?.text = title } } private fun bindSummaryView(holder: PreferenceViewHolder) { summaryView = holder.findViewById(R.id.widget_summary) as TextView - if (!summary.isNullOrEmpty()) { - summaryView.text = if (shouldSummaryBeParsedAsHtmlContent) { - HtmlCompat.fromHtml(summary.toString(), HtmlCompat.FROM_HTML_MODE_COMPACT) - } else { - summary - } + summaryView?.alpha = if (isEnabled) FULL_ALPHA else HALF_ALPHA + summaryView?.let { + if (!summary.isNullOrEmpty()) { + it.text = if (shouldSummaryBeParsedAsHtmlContent) { + HtmlCompat.fromHtml(summary.toString(), HtmlCompat.FROM_HTML_MODE_COMPACT) + } else { + summary + } - summaryView.visibility = View.VISIBLE - } else { - summaryView.visibility = View.GONE + it.visibility = View.VISIBLE + } else { + it.visibility = View.GONE + } } } + + companion object { + const val HALF_ALPHA = 0.5F + const val FULL_ALPHA = 1F + } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt index 89e250e1e..4851b67cb 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt @@ -10,9 +10,12 @@ import androidx.navigation.findNavController import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference +import org.mozilla.fenix.BrowserDirection +import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.getPreferenceKey +import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.settings /** @@ -22,10 +25,13 @@ import org.mozilla.fenix.ext.settings class TrackingProtectionFragment : PreferenceFragmentCompat() { private val exceptionsClickListener = Preference.OnPreferenceClickListener { - val directions = TrackingProtectionFragmentDirections.actionTrackingProtectionFragmentToExceptionsFragment() + val directions = + TrackingProtectionFragmentDirections.actionTrackingProtectionFragmentToExceptionsFragment() view!!.findNavController().navigate(directions) true } + private lateinit var radioStrict: RadioButtonInfoPreference + private lateinit var radioStandard: RadioButtonInfoPreference override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.tracking_protection_preferences, rootKey) @@ -33,7 +39,7 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { override fun onResume() { super.onResume() - activity?.title = getString(R.string.preferences_tracking_protection) + activity?.title = getString(R.string.preference_enhanced_tracking_protection) (activity as AppCompatActivity).supportActionBar?.show() // Tracking Protection Switch @@ -42,7 +48,8 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { preferenceTP?.isChecked = context!!.settings.shouldUseTrackingProtection preferenceTP?.setOnPreferenceChangeListener { preference, trackingProtectionOn -> - preference.context.settings.shouldUseTrackingProtection = trackingProtectionOn + preference.context.settings.shouldUseTrackingProtection = + trackingProtectionOn with(preference.context.components) { val policy = core.createTrackingProtectionPolicy(trackingProtectionOn) useCases.settingsUseCases.updateTrackingProtection(policy) @@ -51,8 +58,72 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { true } + bindStrict() + bindRecommended() + setupRadioGroups() + + val trackingProtectionLearnMore = + context!!.getPreferenceKey(R.string.pref_key_etp_learn_more) + val learnMorePreference = findPreference(trackingProtectionLearnMore) + learnMorePreference?.setOnPreferenceClickListener { + (activity as HomeActivity).openToBrowserAndLoad( + searchTermOrURL = SupportUtils.getGenericSumoURLForTopic + (SupportUtils.SumoTopic.TRACKING_PROTECTION), + newTab = true, + from = BrowserDirection.FromTrackingProtection + ) + true + } + learnMorePreference?.summary = getString( + R.string.preference_enhanced_tracking_protection_explanation, + getString(R.string.app_name) + ) + val exceptions = getPreferenceKey(R.string.pref_key_tracking_protection_exceptions) val preferenceExceptions = findPreference(exceptions) preferenceExceptions?.onPreferenceClickListener = exceptionsClickListener } + + private fun bindStrict() { + val keyStrict = getString(R.string.pref_key_tracking_protection_strict) + radioStrict = requireNotNull(findPreference(keyStrict)) + radioStrict.onInfoClickListener { + nav( + R.id.trackingProtectionFragment, + TrackingProtectionFragmentDirections + .actionTrackingProtectionFragmentToTrackingProtectionBlockingFragment(true) + ) + } + radioStrict.onClickListener { + updateTrackingProtectionPolicy() + } + } + + private fun bindRecommended() { + val keyStandard = getString(R.string.pref_key_tracking_protection_standard) + radioStandard = requireNotNull(findPreference(keyStandard)) + radioStandard.onInfoClickListener { + nav( + R.id.trackingProtectionFragment, + TrackingProtectionFragmentDirections + .actionTrackingProtectionFragmentToTrackingProtectionBlockingFragment(false) + ) + } + radioStandard.onClickListener { + updateTrackingProtectionPolicy() + } + } + + private fun updateTrackingProtectionPolicy() { + context?.components?.let { + val policy = it.core.createTrackingProtectionPolicy() + it.useCases.settingsUseCases.updateTrackingProtection.invoke(policy) + it.useCases.sessionUseCases.reload.invoke() + } + } + + private fun setupRadioGroups() { + radioStandard.addToRadioGroup(radioStrict) + radioStrict.addToRadioGroup(radioStandard) + } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/TrackingProtectionSettingView.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/TrackingProtectionSettingView.kt index 3d745ae1b..6c81facfd 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/TrackingProtectionSettingView.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/TrackingProtectionSettingView.kt @@ -9,9 +9,11 @@ import android.widget.CompoundButton import android.widget.Switch import android.widget.TextView import androidx.appcompat.content.res.AppCompatResources +import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible import io.reactivex.Observer import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds +import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.R import org.mozilla.fenix.ext.settings @@ -20,15 +22,23 @@ class TrackingProtectionSettingView( private val actionEmitter: Observer ) : View.OnClickListener, CompoundButton.OnCheckedChangeListener { private val trackingProtectionSwitch: Switch = container.findViewById(R.id.tracking_protection) - private val trackingProtectionAction: TextView = container.findViewById(R.id.tracking_protection_action) + private val trackingProtectionAction: TextView = + container.findViewById(R.id.tracking_protection_action) + private val trackingProtectionSettingView: ConstraintLayout = + container.findViewById(R.id.tracking_protection_view) init { trackingProtectionSwitch.putCompoundDrawablesRelativeWithIntrinsicBounds( - start = AppCompatResources.getDrawable(container.context, R.drawable.ic_tracking_protection) + start = AppCompatResources.getDrawable( + container.context, + R.drawable.ic_tracking_protection + ) ) } fun bind(isTrackingProtectionOn: Boolean) { + trackingProtectionSettingView.visibility = + if (FeatureFlags.etpCategories) View.GONE else View.VISIBLE val globalTPSetting = trackingProtectionSwitch.context.settings.shouldUseTrackingProtection trackingProtectionAction.isVisible = !globalTPSetting diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/SwitchWithDescription.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/SwitchWithDescription.kt new file mode 100644 index 000000000..0970f47fb --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/SwitchWithDescription.kt @@ -0,0 +1,51 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.trackingprotection + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.withStyledAttributes +import kotlinx.android.synthetic.main.switch_with_description.view.* +import kotlinx.android.synthetic.main.tracking_protection_category.view.switchItemDescription +import kotlinx.android.synthetic.main.tracking_protection_category.view.switchItemTitle +import org.mozilla.fenix.R + +class SwitchWithDescription @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs) { + + init { + LayoutInflater.from(context).inflate(R.layout.switch_with_description, this, true) + + context.withStyledAttributes(attrs, R.styleable.SwitchWithDescription, defStyleAttr, 0) { + val id = getResourceId( + R.styleable.SwitchWithDescription_switchIcon, + R.drawable.ic_tracking_protection + ) + switch_widget?.setCompoundDrawablesWithIntrinsicBounds( + resources.getDrawable( + id, + context.theme + ), null, null, null + ) + switchItemTitle?.text = resources.getString( + getResourceId( + R.styleable.SwitchWithDescription_switchTitle, + R.string.preference_enhanced_tracking_protection + ) + ) + switchItemDescription?.text = resources.getString( + getResourceId( + R.styleable.SwitchWithDescription_switchDescription, + R.string.preference_enhanced_tracking_protection_explanation + ) + ) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionBlockingFragment.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionBlockingFragment.kt new file mode 100644 index 000000000..4a6a56905 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionBlockingFragment.kt @@ -0,0 +1,49 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.trackingprotection + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import kotlinx.android.synthetic.main.fragment_tracking_protection_blocking.* +import org.mozilla.fenix.R + +class TrackingProtectionBlockingFragment : Fragment() { + private val safeArguments get() = requireNotNull(arguments) + + private val isStrict: Boolean by lazy { + TrackingProtectionBlockingFragmentArgs.fromBundle(safeArguments).strictMode + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_tracking_protection_blocking, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + if (isStrict) { + category_tracking_content.visibility = View.VISIBLE + } else { + category_tracking_content.visibility = View.GONE + } + } + + override fun onResume() { + super.onResume() + (activity as AppCompatActivity).title = + getString( + if (isStrict) R.string.preference_enhanced_tracking_protection_strict else + R.string.preference_enhanced_tracking_protection_standard + ) + (activity as AppCompatActivity).supportActionBar?.show() + } +} diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionCategoryItem.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionCategoryItem.kt new file mode 100644 index 000000000..41abe8749 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionCategoryItem.kt @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.trackingprotection + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.withStyledAttributes +import kotlinx.android.synthetic.main.tracking_protection_category.view.* +import org.mozilla.fenix.R + +class TrackingProtectionCategoryItem @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : ConstraintLayout(context, attrs, defStyleAttr) { + init { + LayoutInflater.from(context).inflate(R.layout.tracking_protection_category, this, true) + + context.withStyledAttributes( + attrs, + R.styleable.TrackingProtectionCategory, + defStyleAttr, + 0 + ) { + val id = getResourceId( + R.styleable.TrackingProtectionCategory_categoryItemIcon, + R.drawable.ic_cryptominers + ) + switchIcon?.background = resources.getDrawable(id, context.theme) + switchItemTitle?.text = resources.getString( + getResourceId( + R.styleable.TrackingProtectionCategory_categoryItemTitle, + R.string.etp_cookies_title + ) + ) + switchItemDescription?.text = resources.getString( + getResourceId( + R.styleable.TrackingProtectionCategory_categoryItemDescription, + R.string.etp_cookies_description + ) + ) + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelDialogFragment.kt new file mode 100644 index 000000000..aa611d16a --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelDialogFragment.kt @@ -0,0 +1,216 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.trackingprotection + +import android.app.Dialog +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.LinearLayout +import androidx.appcompat.app.AppCompatDialogFragment +import androidx.appcompat.view.ContextThemeWrapper +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.whenStarted +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import kotlinx.android.synthetic.main.fragment_tracking_protection.view.* +import kotlinx.coroutines.launch +import mozilla.components.browser.session.Session +import mozilla.components.concept.engine.content.blocking.Tracker +import mozilla.components.lib.state.ext.observe +import mozilla.components.support.base.feature.BackHandler +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.R +import org.mozilla.fenix.components.StoreProvider +import org.mozilla.fenix.exceptions.ExceptionDomains +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.nav +import org.mozilla.fenix.ext.requireComponents +import java.net.MalformedURLException +import java.net.URL + +class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), BackHandler { + + private val safeArguments get() = requireNotNull(arguments) + + private val sessionId: String by lazy { + TrackingProtectionPanelDialogFragmentArgs.fromBundle( + safeArguments + ).sessionId + } + + private val url: String by lazy { + TrackingProtectionPanelDialogFragmentArgs.fromBundle(safeArguments).url + } + + private val trackingProtectionEnabled: Boolean by lazy { + TrackingProtectionPanelDialogFragmentArgs.fromBundle(safeArguments) + .trackingProtectionEnabled + } + + private val promptGravity: Int by lazy { + TrackingProtectionPanelDialogFragmentArgs.fromBundle( + safeArguments + ).gravity + } + + private fun inflateRootView(container: ViewGroup? = null): View { + val contextThemeWrapper = ContextThemeWrapper( + activity, + (activity as HomeActivity).themeManager.currentThemeResource + ) + return LayoutInflater.from(contextThemeWrapper).inflate( + R.layout.fragment_tracking_protection, + container, + false + ) + } + + private lateinit var trackingProtectionStore: TrackingProtectionStore + private lateinit var trackingProtectionView: TrackingProtectionPanelView + private lateinit var trackingProtectionInteractor: TrackingProtectionPanelInteractor + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = inflateRootView(container) + val session = requireComponents.core.sessionManager.findSessionById(sessionId) + session?.register(sessionObserver, view = view) + trackingProtectionStore = StoreProvider.get(this) { + TrackingProtectionStore( + TrackingProtectionState( + url, + trackingProtectionEnabled, + session?.trackersBlocked ?: listOf(), + session?.trackersLoaded ?: listOf(), + TrackingProtectionState.Mode.Normal + ) + ) + } + trackingProtectionInteractor = TrackingProtectionPanelInteractor( + trackingProtectionStore, + ::toggleTrackingProtection, + ::openTrackingProtectionSettings + ) + trackingProtectionView = + TrackingProtectionPanelView(view.fragment_tp, trackingProtectionInteractor) + return view + } + + private val sessionObserver = object : Session.Observer { + override fun onUrlChanged(session: Session, url: String) { + trackingProtectionStore.dispatch( + TrackingProtectionAction.UrlChange(url) + ) + } + + override fun onTrackerBlocked(session: Session, tracker: Tracker, all: List) { + trackingProtectionStore.dispatch( + TrackingProtectionAction.TrackerListChange(all) + ) + trackingProtectionStore.dispatch( + TrackingProtectionAction.TrackerLoadedListChange(session.trackersLoaded) + ) + } + + override fun onTrackerLoaded(session: Session, tracker: Tracker, all: List) { + trackingProtectionStore.dispatch( + TrackingProtectionAction.TrackerListChange(session.trackersBlocked) + ) + trackingProtectionStore.dispatch( + TrackingProtectionAction.TrackerLoadedListChange(all) + ) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + trackingProtectionStore.observe(view) { + viewLifecycleOwner.lifecycleScope.launch { + whenStarted { + trackingProtectionView.update(it) + } + } + } + } + + private fun openTrackingProtectionSettings() { + nav( + R.id.trackingProtectionPanelDialogFragment, + TrackingProtectionPanelDialogFragmentDirections + .actionTrackingProtectionPanelDialogFragmentToTrackingProtectionFragment() + ) + } + + private fun toggleTrackingProtection(isEnabled: Boolean) { + context?.let { + val host = try { + URL(url).host + } catch (e: MalformedURLException) { + url + } + lifecycleScope.launch { + if (!ExceptionDomains.load(it).contains(host)) { + ExceptionDomains.add(it, host) + } else { + ExceptionDomains.remove(it, listOf(host)) + } + } + it.components.useCases.sessionUseCases.reload.invoke() + } + trackingProtectionStore.dispatch(TrackingProtectionAction.TrackerBlockingChanged(isEnabled)) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return if (promptGravity == Gravity.BOTTOM) { + object : BottomSheetDialog(requireContext(), this.theme) { + override fun onBackPressed() { + this@TrackingProtectionPanelDialogFragment.onBackPressed() + } + }.apply { + setOnShowListener { + val bottomSheet = + findViewById(com.google.android.material.R.id.design_bottom_sheet) as? FrameLayout + val behavior = BottomSheetBehavior.from(bottomSheet) + behavior.state = BottomSheetBehavior.STATE_EXPANDED + } + } + } else { + object : Dialog(requireContext()) { + override fun onBackPressed() { + this@TrackingProtectionPanelDialogFragment.onBackPressed() + } + }.applyCustomizationsForTopDialog(inflateRootView()) + } + } + + private fun Dialog.applyCustomizationsForTopDialog(rootView: View): Dialog { + addContentView( + rootView, + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT + ) + ) + + window?.apply { + setGravity(promptGravity) + setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + // This must be called after addContentView, or it won't fully fill to the edge. + setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + } + return this + } + + override fun onBackPressed(): Boolean = trackingProtectionView.onBackPressed() +} diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelInteractor.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelInteractor.kt new file mode 100644 index 000000000..d6e326b4a --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelInteractor.kt @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.trackingprotection + +/** + * Interactor for the tracking protection panel + * Provides implementations for the TrackingProtectionPanelViewInteractor + */ +class TrackingProtectionPanelInteractor( + private val store: TrackingProtectionStore, + private val toggleTrackingProtection: (Boolean) -> Unit, + private val openTrackingProtectionSettings: () -> Unit +) : TrackingProtectionPanelViewInteractor { + override fun openDetails(category: TrackingProtectionCategory, categoryBlocked: Boolean) { + store.dispatch(TrackingProtectionAction.EnterDetailsMode(category, categoryBlocked)) + } + + override fun selectTrackingProtectionSettings() { + openTrackingProtectionSettings.invoke() + } + + override fun trackingProtectionToggled(isEnabled: Boolean) { + toggleTrackingProtection.invoke(isEnabled) + } + + override fun onBackPressed() { + store.dispatch(TrackingProtectionAction.ExitDetailsMode) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelView.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelView.kt new file mode 100644 index 000000000..0b85b38f5 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelView.kt @@ -0,0 +1,265 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.trackingprotection + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.net.toUri +import kotlinx.android.extensions.LayoutContainer +import kotlinx.android.synthetic.main.component_tracking_protection_panel.* +import kotlinx.android.synthetic.main.fragment_quick_settings_dialog_sheet.url +import kotlinx.android.synthetic.main.switch_with_description.view.* +import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory.CRYPTOMINING +import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory.FINGERPRINTING +import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory.SOCIAL +import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory.AD +import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory.ANALYTICS +import mozilla.components.concept.engine.content.blocking.Tracker +import mozilla.components.support.ktx.android.net.hostWithoutCommonPrefixes +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.getHostFromUrl +import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.CROSS_SITE_TRACKING_COOKIES +import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.CRYPTOMINERS +import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.FINGERPRINTERS +import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.SOCIAL_MEDIA_TRACKERS +import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.TRACKING_CONTENT + +/** + * Interface for the TrackingProtectionPanelViewInteractor. This interface is implemented by objects that want + * to respond to user interaction on the TrackingProtectionPanelView + */ +interface TrackingProtectionPanelViewInteractor { + /** + * Called whenever the settings option is tapped + */ + fun selectTrackingProtectionSettings() + + /** + * Called whenever the tracking protection toggle for this site is toggled + * @param isEnabled new status of session tracking protection + */ + fun trackingProtectionToggled(isEnabled: Boolean) + + /** + * Called whenever back is pressed + */ + fun onBackPressed() + + /** + * Called whenever an active tracking protection category is tapped + * @param category The Tracking Protection Category to view details about + * @param categoryBlocked The trackers from this category were blocked + */ + fun openDetails(category: TrackingProtectionCategory, categoryBlocked: Boolean) +} + +/** + * View that contains and configures the Tracking Protection Panel + */ +class TrackingProtectionPanelView( + override val containerView: ViewGroup, + val interactor: TrackingProtectionPanelInteractor +) : LayoutContainer { + val view: ConstraintLayout = LayoutInflater.from(containerView.context) + .inflate(R.layout.component_tracking_protection_panel, containerView, true) + .findViewById(R.id.panel_wrapper) + + private val context get() = view.context + + var mode: TrackingProtectionState.Mode = TrackingProtectionState.Mode.Normal + private set + + var trackers: List = listOf() + private set + + var bucketedTrackers: HashMap> = HashMap() + + var loadedTrackers: List = listOf() + private set + + var bucketedLoadedTrackers: HashMap> = HashMap() + + fun update(state: TrackingProtectionState) { + if (state.mode != mode) { + mode = state.mode + } + + if (state.listTrackers != trackers) { + trackers = state.listTrackers + bucketedTrackers = getHashMapOfTrackersForCategory(state.listTrackers) + } + + if (state.listTrackersLoaded != loadedTrackers) { + loadedTrackers = state.listTrackersLoaded + bucketedLoadedTrackers = getHashMapOfTrackersForCategory(state.listTrackersLoaded) + } + + when (val mode = state.mode) { + is TrackingProtectionState.Mode.Normal -> setUIForNormalMode(state) + is TrackingProtectionState.Mode.Details -> setUIForDetailsMode( + mode.selectedCategory, + mode.categoryBlocked + ) + } + } + + private fun setUIForNormalMode(state: TrackingProtectionState) { + details_mode.visibility = View.GONE + normal_mode.visibility = View.VISIBLE + + not_blocking_header.visibility = + if (bucketedLoadedTrackers.size == 0) View.GONE else View.VISIBLE + bindUrl(state.url) + bindTrackingProtectionInfo(state.isTrackingProtectionEnabled) + protection_settings.setOnClickListener { + interactor.selectTrackingProtectionSettings() + } + + blocking_header.visibility = + if (bucketedTrackers.size == 0) View.GONE else View.VISIBLE + updateCategoryVisibility() + setCategoryClickListeners() + } + + @Suppress("ComplexMethod") + private fun updateCategoryVisibility() { + cross_site_tracking.visibility = bucketedTrackers.getVisibility(CROSS_SITE_TRACKING_COOKIES) + social_media_trackers.visibility = bucketedTrackers.getVisibility(SOCIAL_MEDIA_TRACKERS) + fingerprinters.visibility = bucketedTrackers.getVisibility(FINGERPRINTERS) + tracking_content.visibility = bucketedTrackers.getVisibility(TRACKING_CONTENT) + cryptominers.visibility = bucketedTrackers.getVisibility(CRYPTOMINERS) + + cross_site_tracking_loaded.visibility = + bucketedLoadedTrackers.getVisibility(CROSS_SITE_TRACKING_COOKIES) + social_media_trackers_loaded.visibility = + bucketedLoadedTrackers.getVisibility(SOCIAL_MEDIA_TRACKERS) + fingerprinters_loaded.visibility = bucketedLoadedTrackers.getVisibility(FINGERPRINTERS) + tracking_content_loaded.visibility = bucketedLoadedTrackers.getVisibility(TRACKING_CONTENT) + cryptominers_loaded.visibility = bucketedLoadedTrackers.getVisibility(CRYPTOMINERS) + } + + private fun HashMap>.getVisibility( + category: TrackingProtectionCategory + ): Int = if (this[category]?.isNotEmpty() == true) View.VISIBLE else View.GONE + + private fun setCategoryClickListeners() { + social_media_trackers.setOnClickListener { + interactor.openDetails(SOCIAL_MEDIA_TRACKERS, categoryBlocked = true) + } + fingerprinters.setOnClickListener { + interactor.openDetails(FINGERPRINTERS, categoryBlocked = true) + } + cross_site_tracking.setOnClickListener { + interactor.openDetails(CROSS_SITE_TRACKING_COOKIES, categoryBlocked = true) + } + tracking_content.setOnClickListener { + interactor.openDetails(TRACKING_CONTENT, categoryBlocked = true) + } + cryptominers.setOnClickListener { + interactor.openDetails(CRYPTOMINERS, categoryBlocked = true) + } + social_media_trackers_loaded.setOnClickListener { + interactor.openDetails(SOCIAL_MEDIA_TRACKERS, categoryBlocked = false) + } + fingerprinters_loaded.setOnClickListener { + interactor.openDetails(FINGERPRINTERS, categoryBlocked = false) + } + cross_site_tracking_loaded.setOnClickListener { + interactor.openDetails(CROSS_SITE_TRACKING_COOKIES, categoryBlocked = false) + } + tracking_content_loaded.setOnClickListener { + interactor.openDetails(TRACKING_CONTENT, categoryBlocked = false) + } + cryptominers_loaded.setOnClickListener { + interactor.openDetails(CRYPTOMINERS, categoryBlocked = false) + } + } + + private fun setUIForDetailsMode( + category: TrackingProtectionCategory, + categoryBlocked: Boolean + ) { + normal_mode.visibility = View.GONE + details_mode.visibility = View.VISIBLE + category_title.text = context.getString(category.title) + val stringList = bucketedTrackers[category]?.joinToString("\n") + blocking_text_list.text = stringList + category_description.text = context.getString(category.description) + details_blocking_header.text = + context.getString( + if (categoryBlocked) R.string.enhanced_tracking_protection_blocked else + R.string.enhanced_tracking_protection_allowed + ) + details_back.setOnClickListener { + interactor.onBackPressed() + } + } + + private fun getHashMapOfTrackersForCategory( + list: List + ): HashMap> { + val hashMap = HashMap>() + items@ for (item in list) { + when { + item.trackingCategories.contains(CRYPTOMINING) -> { + hashMap[CRYPTOMINERS] = + (hashMap[CRYPTOMINERS] + ?: listOf()).plus(item.url.getHostFromUrl() ?: item.url) + continue@items + } + item.trackingCategories.contains(FINGERPRINTING) -> { + hashMap[FINGERPRINTERS] = + (hashMap[FINGERPRINTERS] + ?: listOf()).plus(item.url.getHostFromUrl() ?: item.url) + continue@items + } + item.trackingCategories.contains(SOCIAL) -> { + hashMap[SOCIAL_MEDIA_TRACKERS] = + (hashMap[SOCIAL_MEDIA_TRACKERS] ?: listOf()).plus( + item.url.getHostFromUrl() ?: item.url + ) + continue@items + } + item.trackingCategories.contains(AD) || + item.trackingCategories.contains(SOCIAL) || + item.trackingCategories.contains(ANALYTICS) -> { + hashMap[TRACKING_CONTENT] = + (hashMap[TRACKING_CONTENT] ?: listOf()).plus( + item.url.getHostFromUrl() ?: item.url + ) + continue@items + } + } + } + return hashMap + } + + private fun bindUrl(url: String) { + this.url.text = url.toUri().hostWithoutCommonPrefixes + } + + private fun bindTrackingProtectionInfo(isTrackingProtectionOn: Boolean) { + tracking_protection.switchItemDescription.text = + context.getString(if (isTrackingProtectionOn) R.string.etp_panel_on else R.string.etp_panel_off) + tracking_protection.switch_widget.isChecked = isTrackingProtectionOn + + tracking_protection.switch_widget.setOnCheckedChangeListener { _, isChecked -> + interactor.trackingProtectionToggled(isChecked) + } + } + + fun onBackPressed(): Boolean { + return when (mode) { + is TrackingProtectionState.Mode.Details -> { + mode = TrackingProtectionState.Mode.Normal + interactor.onBackPressed() + true + } + else -> false + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStore.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStore.kt new file mode 100644 index 000000000..2fab0a34e --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStore.kt @@ -0,0 +1,129 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.trackingprotection + +import mozilla.components.concept.engine.content.blocking.Tracker +import mozilla.components.lib.state.Action +import mozilla.components.lib.state.State +import mozilla.components.lib.state.Store +import org.mozilla.fenix.R + +/** + * The [Store] for holding the [TrackingProtectionState] and applying [TrackingProtectionAction]s. + */ +class TrackingProtectionStore(initialState: TrackingProtectionState) : + Store( + initialState, + ::trackingProtectionStateReducer + ) + +/** + * Actions to dispatch through the `TrackingProtectionStore` to modify `TrackingProtectionState` through the reducer. + */ +sealed class TrackingProtectionAction : Action { + data class Change( + val url: String, + val isTrackingProtectionEnabled: Boolean, + val listTrackers: List, + val listTrackersLoaded: List, + val mode: TrackingProtectionState.Mode + ) : TrackingProtectionAction() + + data class UrlChange(val url: String) : TrackingProtectionAction() + data class TrackerListChange(val listTrackers: List) : TrackingProtectionAction() + data class TrackerLoadedListChange(val listTrackersLoaded: List) : + TrackingProtectionAction() + + data class TrackerBlockingChanged(val isTrackingProtectionEnabled: Boolean) : + TrackingProtectionAction() + + object ExitDetailsMode : TrackingProtectionAction() + data class EnterDetailsMode( + val category: TrackingProtectionCategory, + val categoryBlocked: Boolean + ) : + TrackingProtectionAction() +} + +/** + * The state for the Tracking Protection Panel + * @property url Current URL to display + * @property isTrackingProtectionEnabled Current status of tracking protection for this session (ie is an exception) + * @property listTrackers List of currently blocked Trackers + * @property listTrackersLoaded List of currently not blocked Trackers + * @property mode Current Mode of TrackingProtection + */ +data class TrackingProtectionState( + val url: String, + val isTrackingProtectionEnabled: Boolean, + val listTrackers: List, + val listTrackersLoaded: List, + val mode: Mode +) : State { + sealed class Mode { + object Normal : Mode() + data class Details( + val selectedCategory: TrackingProtectionCategory, + val categoryBlocked: Boolean + ) : Mode() + } +} + +/** + * The 5 categories of Tracking Protection to display + */ +enum class TrackingProtectionCategory(val title: Int, val description: Int) { + SOCIAL_MEDIA_TRACKERS( + R.string.etp_social_media_trackers_title, + R.string.etp_social_media_trackers_description + ), + CROSS_SITE_TRACKING_COOKIES( + R.string.etp_cookies_title, + R.string.etp_cookies_description + ), + CRYPTOMINERS( + R.string.etp_cryptominers_title, + R.string.etp_cryptominers_description + ), + FINGERPRINTERS(R.string.etp_fingerprinters_title, R.string.etp_fingerprinters_description), + TRACKING_CONTENT(R.string.etp_tracking_content_title, R.string.etp_tracking_content_description) +} + +/** + * The TrackingProtectionState Reducer. + */ +fun trackingProtectionStateReducer( + state: TrackingProtectionState, + action: TrackingProtectionAction +): TrackingProtectionState { + return when (action) { + is TrackingProtectionAction.Change -> state.copy( + url = action.url, + isTrackingProtectionEnabled = action.isTrackingProtectionEnabled, + listTrackers = action.listTrackers, + mode = action.mode + ) + is TrackingProtectionAction.UrlChange -> state.copy( + url = action.url + ) + is TrackingProtectionAction.TrackerListChange -> state.copy( + listTrackers = action.listTrackers + ) + is TrackingProtectionAction.TrackerLoadedListChange -> state.copy( + listTrackersLoaded = action.listTrackersLoaded + ) + TrackingProtectionAction.ExitDetailsMode -> state.copy( + mode = TrackingProtectionState.Mode.Normal + ) + is TrackingProtectionAction.EnterDetailsMode -> state.copy( + mode = TrackingProtectionState.Mode.Details( + action.category, + action.categoryBlocked + ) + ) + is TrackingProtectionAction.TrackerBlockingChanged -> + state.copy(isTrackingProtectionEnabled = action.isTrackingProtectionEnabled) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index 2aa297444..1d774ca29 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -29,9 +29,9 @@ class Settings private constructor( context: Context, private val isCrashReportEnabledInBuild: Boolean ) : PreferencesHolder { - companion object { const val autoBounceMaximumCount = 2 + const val trackingProtectionOnboardingMaximumCount = 2 const val FENIX_PREFERENCES = "fenix_preferences" private const val BLOCKED_INT = 0 private const val ASK_TO_ALLOW_INT = 1 @@ -80,7 +80,10 @@ class Settings private constructor( val isCrashReportingEnabled: Boolean get() = isCrashReportEnabledInBuild && - preferences.getBoolean(appContext.getPreferenceKey(R.string.pref_key_crash_reporter), true) + preferences.getBoolean( + appContext.getPreferenceKey(R.string.pref_key_crash_reporter), + true + ) val isRemoteDebuggingEnabled by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_remote_debugging), @@ -97,6 +100,12 @@ class Settings private constructor( default = true ) + private var trackingProtectionOnboardingShownThisSession = false + + val shouldShowTrackingProtectionOnboarding: Boolean + get() = trackingProtectionOnboardingCount < trackingProtectionOnboardingMaximumCount && + !trackingProtectionOnboardingShownThisSession + val shouldAutoBounceQuickActionSheet: Boolean get() = autoBounceQuickActionSheetCount < autoBounceMaximumCount @@ -150,6 +159,11 @@ class Settings private constructor( default = false ) + val useStrictTrackingProtection by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_tracking_protection_strict), + false + ) + val themeSettingString: String get() = when { shouldFollowDeviceTheme -> appContext.getString(R.string.preference_follow_device_theme) @@ -177,10 +191,27 @@ class Settings private constructor( default = true ) + @VisibleForTesting(otherwise = PRIVATE) + internal val trackingProtectionOnboardingCount by intPreference( + appContext.getPreferenceKey(R.string.pref_key_tracking_protection_onboarding), + 0 + ) + + fun incrementTrackingProtectionOnboardingCount() { + trackingProtectionOnboardingShownThisSession = true + preferences.edit().putInt( + appContext.getPreferenceKey(R.string.pref_key_tracking_protection_onboarding), + trackingProtectionOnboardingCount + 1 + ).apply() + } + fun getSitePermissionsPhoneFeatureAction(feature: PhoneFeature) = intToAction(preferences.getInt(feature.getPreferenceKey(appContext), ASK_TO_ALLOW_INT)) - fun setSitePermissionsPhoneFeatureAction(feature: PhoneFeature, value: SitePermissionsRules.Action) { + fun setSitePermissionsPhoneFeatureAction( + feature: PhoneFeature, + value: SitePermissionsRules.Action + ) { preferences.edit().putInt(feature.getPreferenceKey(appContext), actionToInt(value)).apply() } @@ -212,5 +243,8 @@ class Settings private constructor( } val searchWidgetInstalled: Boolean - get() = 0 < preferences.getInt(appContext.getPreferenceKey(R.string.pref_key_search_widget_installed), 0) + get() = 0 < preferences.getInt( + appContext.getPreferenceKey(R.string.pref_key_search_widget_installed), + 0 + ) } diff --git a/app/src/main/res/drawable/ic_arrowhead_right.xml b/app/src/main/res/drawable/ic_arrowhead_right.xml new file mode 100644 index 000000000..b7bc0e711 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrowhead_right.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_cookies.xml b/app/src/main/res/drawable/ic_cookies.xml new file mode 100644 index 000000000..553f16518 --- /dev/null +++ b/app/src/main/res/drawable/ic_cookies.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_cryptominers.xml b/app/src/main/res/drawable/ic_cryptominers.xml new file mode 100644 index 000000000..9d0aba381 --- /dev/null +++ b/app/src/main/res/drawable/ic_cryptominers.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_etp_artwork.xml b/app/src/main/res/drawable/ic_etp_artwork.xml new file mode 100644 index 000000000..e07107874 --- /dev/null +++ b/app/src/main/res/drawable/ic_etp_artwork.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_fingerprinters.xml b/app/src/main/res/drawable/ic_fingerprinters.xml new file mode 100644 index 000000000..9a4652791 --- /dev/null +++ b/app/src/main/res/drawable/ic_fingerprinters.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml index c938961a2..23964049b 100644 --- a/app/src/main/res/drawable/ic_info.xml +++ b/app/src/main/res/drawable/ic_info.xml @@ -7,7 +7,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - + diff --git a/app/src/main/res/drawable/ic_social_media_trackers.xml b/app/src/main/res/drawable/ic_social_media_trackers.xml new file mode 100644 index 000000000..fe473248a --- /dev/null +++ b/app/src/main/res/drawable/ic_social_media_trackers.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_tracking_content.xml b/app/src/main/res/drawable/ic_tracking_content.xml new file mode 100644 index 000000000..f92643c34 --- /dev/null +++ b/app/src/main/res/drawable/ic_tracking_content.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_tracking_protection_disabled.xml b/app/src/main/res/drawable/ic_tracking_protection_disabled.xml index 1bdb069ad..0ef9a1b5c 100644 --- a/app/src/main/res/drawable/ic_tracking_protection_disabled.xml +++ b/app/src/main/res/drawable/ic_tracking_protection_disabled.xml @@ -3,25 +3,25 @@ - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> - + + android:name="strike_thru_path" + android:fillColor="@color/disabled_text" + android:pathData="M 20 1.6 L 21.4 3 L 5.2 19.2 C 4.2 20.2 2.8 18.8 3.8 17.8 L 20 1.6 Z" + android:strokeWidth="1" /> + android:name="strike_thru_mask" + android:pathData="M 0 0 L 0 24 L 24 24 L 24 0 L 0 0 Z M 21 2 L 23 4 L 6 21 L 4 19 L 21 2 Z" /> + android:name="icon" + android:fillColor="@color/disabled_text" + android:pathData="M20 6c0-1-0.8-1.9-1.8-2L12 3 5.8 4C4.8 4 4 5 4 6l0.1 5c0.3 3.2 1 5 2.5 7a8.4 8.4 0 0 0 5.3 3h0.2c2.1-0.3 4-1.4 5.3-3 1.6-2 2.2-3.8 2.5-7l0.1-5zm-2.1 4.8a10 10 0 0 1-2 6c-1 1.1-2.4 2-3.9 2.3a6.5 6.5 0 0 1-3.9-2.4 9.9 9.9 0 0 1-2-5.9 67.3 67.3 0 0 1 0-4.9L12 5l5.9 1 0.1 0.2-0.1 4.7zM8 7.6v3c0.3 2.7 0.8 3.7 1.7 5 0.6 0.6 1.4 1.2 2.3 1.4V7l-4 0.6z" /> diff --git a/app/src/main/res/drawable/ic_triangle.xml b/app/src/main/res/drawable/ic_triangle.xml new file mode 100644 index 000000000..d7fd2b45a --- /dev/null +++ b/app/src/main/res/drawable/ic_triangle.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/onboarding_popup_background.xml b/app/src/main/res/drawable/onboarding_popup_background.xml new file mode 100644 index 000000000..9c47ca2bc --- /dev/null +++ b/app/src/main/res/drawable/onboarding_popup_background.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/app/src/main/res/layout/component_tracking_protection_panel.xml b/app/src/main/res/layout/component_tracking_protection_panel.xml new file mode 100644 index 000000000..24eee13aa --- /dev/null +++ b/app/src/main/res/layout/component_tracking_protection_panel.xml @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/exceptions_description.xml b/app/src/main/res/layout/exceptions_description.xml index 8ebfaf931..ab1847f31 100644 --- a/app/src/main/res/layout/exceptions_description.xml +++ b/app/src/main/res/layout/exceptions_description.xml @@ -7,6 +7,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="12dp" - android:text="@string/preferences_tracking_protection_exceptions_description" + android:text="@string/enhanced_tracking_protection_exceptions" android:textColor="?primaryText" android:textSize="16sp" /> diff --git a/app/src/main/res/layout/fragment_quick_settings_dialog_sheet.xml b/app/src/main/res/layout/fragment_quick_settings_dialog_sheet.xml index 9700c7709..9003da9d7 100644 --- a/app/src/main/res/layout/fragment_quick_settings_dialog_sheet.xml +++ b/app/src/main/res/layout/fragment_quick_settings_dialog_sheet.xml @@ -22,22 +22,22 @@ style="@style/QuickSettingsText" android:layout_width="0dp" android:layout_height="@dimen/quicksettings_item_height" + app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - tools:text="https://wikipedia.org" - app:layout_constraintEnd_toEndOf="parent" /> + tools:text="https://wikipedia.org" /> + tools:text="Secure connection" /> - - - - - - - + app:layout_constraintTop_toBottomOf="@id/line_divider_security"> + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@id/tracking_protection_view" /> + app:layout_constraintTop_toBottomOf="@id/camera_icon" /> + app:layout_constraintTop_toBottomOf="@id/microphone_icon" /> + app:layout_constraintTop_toBottomOf="@id/notification_icon" /> + + diff --git a/app/src/main/res/layout/fragment_tracking_protection_blocking.xml b/app/src/main/res/layout/fragment_tracking_protection_blocking.xml new file mode 100644 index 000000000..857fa8973 --- /dev/null +++ b/app/src/main/res/layout/fragment_tracking_protection_blocking.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/preference_widget_radiobutton.xml b/app/src/main/res/layout/preference_widget_radiobutton.xml index 86303fe98..544bbe197 100644 --- a/app/src/main/res/layout/preference_widget_radiobutton.xml +++ b/app/src/main/res/layout/preference_widget_radiobutton.xml @@ -6,7 +6,7 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/switch_with_description.xml b/app/src/main/res/layout/switch_with_description.xml new file mode 100644 index 000000000..7b484e98a --- /dev/null +++ b/app/src/main/res/layout/switch_with_description.xml @@ -0,0 +1,53 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/tracking_protection_category.xml b/app/src/main/res/layout/tracking_protection_category.xml new file mode 100644 index 000000000..b5f06c576 --- /dev/null +++ b/app/src/main/res/layout/tracking_protection_category.xml @@ -0,0 +1,57 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/tracking_protection_learn_more_preference.xml b/app/src/main/res/layout/tracking_protection_learn_more_preference.xml new file mode 100644 index 000000000..ef4199375 --- /dev/null +++ b/app/src/main/res/layout/tracking_protection_learn_more_preference.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/tracking_protection_onboarding_popup.xml b/app/src/main/res/layout/tracking_protection_onboarding_popup.xml new file mode 100644 index 000000000..aa1490cb0 --- /dev/null +++ b/app/src/main/res/layout/tracking_protection_onboarding_popup.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 724bfa82a..2ab7193be 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -100,8 +100,8 @@ app:nullable="true" /> + android:defaultValue="false" + app:argType="boolean" /> + + android:id="@+id/externalAppBrowserFragment" + android:name="org.mozilla.fenix.customtabs.ExternalAppBrowserFragment" + tools:layout="@layout/fragment_browser"> + android:name="activeSessionId" + app:argType="string" + app:nullable="true" /> + android:id="@+id/action_externalAppBrowserFragment_to_shareFragment" + app:destination="@id/shareFragment" /> + android:id="@+id/action_externalAppBrowserFragment_to_quickSettingsSheetDialogFragment" + app:destination="@id/quickSettingsSheetDialogFragment" /> + - + android:label="@string/preferences_sync"> + android:label="AboutFragment"> @@ -414,7 +419,7 @@ + app:popUpTo="@id/nav_graph" /> @@ -430,6 +435,12 @@ + + - + + + + + + + + + + + diff --git a/app/src/main/res/raw-night/shield_json.json b/app/src/main/res/raw-night/shield_json.json new file mode 100644 index 000000000..fed006596 --- /dev/null +++ b/app/src/main/res/raw-night/shield_json.json @@ -0,0 +1,1582 @@ +{ + "v": "5.5.7", + "fr": 30, + "ip": 0, + "op": 17, + "w": 24, + "h": 24, + "nm": "Shield - Final 24x24 (on Dark)", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Shape Layer 1", + "td": 1, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 12, + 12, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 150, + 150, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + -0.938, + -1.062 + ], + [ + -1.312, + 0 + ], + [ + -0.812, + 1.062 + ], + [ + 0, + 0 + ], + [ + 2.188, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0.938, + 1.062 + ], + [ + 1.312, + 0 + ], + [ + 0.812, + -1.062 + ], + [ + 0, + 0 + ], + [ + -2.188, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -4.938, + -3.875 + ], + [ + -4.625, + 1.688 + ], + [ + -0.062, + 5.938 + ], + [ + 4.625, + 1.812 + ], + [ + 5, + -4.375 + ], + [ + 0, + -5.938 + ], + [ + -4.375, + -4.875 + ] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 0, + "k": 0, + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.392 + ], + "y": [ + 0 + ] + }, + "t": 0, + "s": [ + 0 + ] + }, + { + "t": 16, + "s": [ + 100 + ] + } + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0.843137015548, + 0, + 0.133333004222, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 3, + "ix": 5 + }, + "lc": 2, + "lj": 2, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Shape 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 18, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Blue Shield", + "tt": 1, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 12, + 12, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 6, + 7, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 150, + 150, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.971, + 1.298 + ], + [ + 0.042, + 1.905 + ], + [ + 0, + 1.025 + ], + [ + -0.884, + 0.155 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.003, + -0.899 + ], + [ + 0.08, + -0.839 + ], + [ + 1.13, + -1.536 + ], + [ + 1.602, + -0.257 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + -1.602, + -0.257 + ], + [ + -1.129, + -1.536 + ], + [ + -0.076, + -0.839 + ], + [ + -0.002, + -0.898 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.885, + 0.154 + ], + [ + 0, + 1.023 + ], + [ + -0.042, + 1.905 + ], + [ + -0.972, + 1.298 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 0, + 7.006 + ], + [ + -0.112, + 6.994 + ], + [ + -4.124, + 4.568 + ], + [ + -5.924, + -0.719 + ], + [ + -6, + -4.425 + ], + [ + -4.473, + -6.247 + ], + [ + 0, + -7.015 + ], + [ + 4.471, + -6.247 + ], + [ + 6, + -4.424 + ], + [ + 5.92, + -0.719 + ], + [ + 4.12, + 4.568 + ], + [ + 0.108, + 6.994 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 1, + "ty": "sh", + "ix": 2, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + -0.049, + -0.54 + ], + [ + -0.941, + -1.27 + ], + [ + -1.005, + -0.211 + ], + [ + -0.615, + 0.823 + ], + [ + -0.026, + 1.58 + ], + [ + 0, + 1.625 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 1.625 + ], + [ + 0.026, + 1.58 + ], + [ + 0.615, + 0.823 + ], + [ + 1.005, + -0.211 + ], + [ + 0.941, + -1.27 + ], + [ + 0.049, + -0.54 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -4, + -4.3 + ], + [ + -4, + -1 + ], + [ + -2.513, + 3.382 + ], + [ + 0, + 4.987 + ], + [ + 2.512, + 3.382 + ], + [ + 4, + -1 + ], + [ + 4, + -4.3 + ], + [ + 0, + -4.985 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 2", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "mm", + "mm": 1, + "nm": "Merge Paths 1", + "mn": "ADBE Vector Filter - Merge", + "hd": false + }, + { + "ty": "gf", + "o": { + "a": 0, + "k": 100, + "ix": 10 + }, + "r": 1, + "bm": 0, + "g": { + "p": 3, + "k": { + "a": 0, + "k": [ + 0, + 0.776, + 0.537, + 1, + 0.5, + 0.388, + 0.62, + 0.978, + 1, + 0, + 0.702, + 0.957 + ], + "ix": 9 + } + }, + "s": { + "a": 0, + "k": [ + 6.088, + -6.87 + ], + "ix": 5 + }, + "e": { + "a": 0, + "k": [ + -5.96, + 7.121 + ], + "ix": 6 + }, + "t": 1, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 6, + 7 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 6, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 18, + "st": -10, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Fill", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 9.75, + 12, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 1.5, + 4, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 150, + 150, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.032, + -0.345 + ], + [ + -0.755, + -1.021 + ], + [ + -0.519, + -0.174 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0.009, + 0.942 + ], + [ + -0.022, + 1.269 + ], + [ + 0.327, + 0.439 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 1.25, + -3.451 + ], + [ + -1.25, + -3.023 + ], + [ + -1.188, + -1.023 + ], + [ + -0.058, + 2.508 + ], + [ + 1.242, + 3.451 + ], + [ + 1.25, + 3.451 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "gf", + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 3, + "s": [ + 0 + ] + }, + { + "t": 13, + "s": [ + 100 + ] + } + ], + "ix": 10 + }, + "r": 1, + "bm": 0, + "g": { + "p": 3, + "k": { + "a": 0, + "k": [ + 0, + 0.776, + 0.537, + 1, + 0.5, + 0.388, + 0.62, + 0.978, + 1, + 0, + 0.702, + 0.957 + ], + "ix": 9 + } + }, + "s": { + "a": 0, + "k": [ + 7.296, + -6.978 + ], + "ix": 5 + }, + "e": { + "a": 0, + "k": [ + -4.711, + 6.943 + ], + "ix": 6 + }, + "t": 1, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-Fill", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.501960813999, + 0.501960813999, + 0.556862771511, + 1 + ], + "ix": 4 + }, + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 9, + "s": [ + 99 + ] + }, + { + "t": 14, + "s": [ + 0 + ] + } + ], + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 1.75, + 3.988 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 18, + "st": -10, + "bm": 0 + }, + { + "ddd": 0, + "ind": 4, + "ty": 4, + "nm": "Grey Shield", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 13, + "s": [ + 100 + ] + }, + { + "t": 17, + "s": [ + 0 + ] + } + ], + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 12, + 12, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 6, + 7, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 150, + 150, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.971, + 1.298 + ], + [ + 0.042, + 1.905 + ], + [ + 0, + 1.025 + ], + [ + -0.884, + 0.155 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.003, + -0.899 + ], + [ + 0.08, + -0.839 + ], + [ + 1.13, + -1.536 + ], + [ + 1.602, + -0.257 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + -1.602, + -0.257 + ], + [ + -1.129, + -1.536 + ], + [ + -0.076, + -0.839 + ], + [ + -0.002, + -0.898 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.885, + 0.154 + ], + [ + 0, + 1.023 + ], + [ + -0.042, + 1.905 + ], + [ + -0.972, + 1.298 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 0, + 7.006 + ], + [ + -0.112, + 6.994 + ], + [ + -4.124, + 4.568 + ], + [ + -5.924, + -0.719 + ], + [ + -6, + -4.425 + ], + [ + -4.473, + -6.247 + ], + [ + 0, + -7.015 + ], + [ + 4.471, + -6.247 + ], + [ + 6, + -4.424 + ], + [ + 5.92, + -0.719 + ], + [ + 4.12, + 4.568 + ], + [ + 0.108, + 6.994 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 1, + "ty": "sh", + "ix": 2, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + -0.049, + -0.54 + ], + [ + -0.941, + -1.27 + ], + [ + -1.005, + -0.211 + ], + [ + -0.615, + 0.823 + ], + [ + -0.026, + 1.58 + ], + [ + 0, + 1.625 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 1.625 + ], + [ + 0.026, + 1.58 + ], + [ + 0.615, + 0.823 + ], + [ + 1.005, + -0.211 + ], + [ + 0.941, + -1.27 + ], + [ + 0.049, + -0.54 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -4, + -4.3 + ], + [ + -4, + -1 + ], + [ + -2.513, + 3.382 + ], + [ + 0, + 4.987 + ], + [ + 2.512, + 3.382 + ], + [ + 4, + -1 + ], + [ + 4, + -4.3 + ], + [ + 0, + -4.985 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 2", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "mm", + "mm": 1, + "nm": "Merge Paths 1", + "mn": "ADBE Vector Filter - Merge", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.501960813999, + 0.501960813999, + 0.556862771511, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 6, + 7 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 6, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 18, + "st": -10, + "bm": 0 + } + ], + "markers": [] +} diff --git a/app/src/main/res/raw/shield_json.json b/app/src/main/res/raw/shield_json.json new file mode 100644 index 000000000..d4173ab3c --- /dev/null +++ b/app/src/main/res/raw/shield_json.json @@ -0,0 +1,1582 @@ +{ + "v": "5.5.5", + "fr": 30, + "ip": 0, + "op": 17, + "w": 24, + "h": 24, + "nm": "Shield - Final for export (updated colours - Amy)", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Shape Layer 1", + "td": 1, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 12, + 12, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 150, + 150, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + -0.938, + -1.062 + ], + [ + -1.312, + 0 + ], + [ + -0.812, + 1.062 + ], + [ + 0, + 0 + ], + [ + 2.188, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0.938, + 1.062 + ], + [ + 1.312, + 0 + ], + [ + 0.812, + -1.062 + ], + [ + 0, + 0 + ], + [ + -2.188, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -4.938, + -3.875 + ], + [ + -4.625, + 1.688 + ], + [ + -0.062, + 5.938 + ], + [ + 4.625, + 1.812 + ], + [ + 5, + -4.375 + ], + [ + 0, + -5.938 + ], + [ + -4.375, + -4.875 + ] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 0, + "k": 0, + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.392 + ], + "y": [ + 0 + ] + }, + "t": 0, + "s": [ + 0 + ] + }, + { + "t": 16, + "s": [ + 100 + ] + } + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0.843137015548, + 0, + 0.133333004222, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 3, + "ix": 5 + }, + "lc": 2, + "lj": 2, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Shape 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 18, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Blue Shield", + "tt": 1, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 12, + 12, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 6, + 7, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 150, + 150, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.971, + 1.298 + ], + [ + 0.042, + 1.905 + ], + [ + 0, + 1.025 + ], + [ + -0.884, + 0.155 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.003, + -0.899 + ], + [ + 0.08, + -0.839 + ], + [ + 1.13, + -1.536 + ], + [ + 1.602, + -0.257 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + -1.602, + -0.257 + ], + [ + -1.129, + -1.536 + ], + [ + -0.076, + -0.839 + ], + [ + -0.002, + -0.898 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.885, + 0.154 + ], + [ + 0, + 1.023 + ], + [ + -0.042, + 1.905 + ], + [ + -0.972, + 1.298 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 0, + 7.006 + ], + [ + -0.112, + 6.994 + ], + [ + -4.124, + 4.568 + ], + [ + -5.924, + -0.719 + ], + [ + -6, + -4.425 + ], + [ + -4.473, + -6.247 + ], + [ + 0, + -7.015 + ], + [ + 4.471, + -6.247 + ], + [ + 6, + -4.424 + ], + [ + 5.92, + -0.719 + ], + [ + 4.12, + 4.568 + ], + [ + 0.108, + 6.994 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 1, + "ty": "sh", + "ix": 2, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + -0.049, + -0.54 + ], + [ + -0.941, + -1.27 + ], + [ + -1.005, + -0.211 + ], + [ + -0.615, + 0.823 + ], + [ + -0.026, + 1.58 + ], + [ + 0, + 1.625 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 1.625 + ], + [ + 0.026, + 1.58 + ], + [ + 0.615, + 0.823 + ], + [ + 1.005, + -0.211 + ], + [ + 0.941, + -1.27 + ], + [ + 0.049, + -0.54 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -4, + -4.3 + ], + [ + -4, + -1 + ], + [ + -2.513, + 3.382 + ], + [ + 0, + 4.987 + ], + [ + 2.512, + 3.382 + ], + [ + 4, + -1 + ], + [ + 4, + -4.3 + ], + [ + 0, + -4.985 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 2", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "mm", + "mm": 1, + "nm": "Merge Paths 1", + "mn": "ADBE Vector Filter - Merge", + "hd": false + }, + { + "ty": "gf", + "o": { + "a": 0, + "k": 100, + "ix": 10 + }, + "r": 1, + "bm": 0, + "g": { + "p": 3, + "k": { + "a": 0, + "k": [ + 0, + 0.565, + 0.349, + 1, + 0.5, + 0.286, + 0.331, + 0.867, + 1, + 0.008, + 0.314, + 0.733 + ], + "ix": 9 + } + }, + "s": { + "a": 0, + "k": [ + 6.088, + -6.87 + ], + "ix": 5 + }, + "e": { + "a": 0, + "k": [ + -5.96, + 7.121 + ], + "ix": 6 + }, + "t": 1, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 6, + 7 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 6, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 18, + "st": -10, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Fill", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 9.75, + 12, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 1.5, + 4, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 150, + 150, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.032, + -0.345 + ], + [ + -0.755, + -1.021 + ], + [ + -0.519, + -0.174 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0.009, + 0.942 + ], + [ + -0.022, + 1.269 + ], + [ + 0.327, + 0.439 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 1.25, + -3.451 + ], + [ + -1.25, + -3.023 + ], + [ + -1.188, + -1.023 + ], + [ + -0.058, + 2.508 + ], + [ + 1.242, + 3.451 + ], + [ + 1.25, + 3.451 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "gf", + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 3, + "s": [ + 0 + ] + }, + { + "t": 13, + "s": [ + 100 + ] + } + ], + "ix": 10 + }, + "r": 1, + "bm": 0, + "g": { + "p": 3, + "k": { + "a": 0, + "k": [ + 0, + 0.565, + 0.349, + 1, + 0.5, + 0.286, + 0.331, + 0.867, + 1, + 0.008, + 0.314, + 0.733 + ], + "ix": 9 + } + }, + "s": { + "a": 0, + "k": [ + 7.296, + -6.978 + ], + "ix": 5 + }, + "e": { + "a": 0, + "k": [ + -4.711, + 6.943 + ], + "ix": 6 + }, + "t": 1, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-Fill", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.501960813999, + 0.501960813999, + 0.556862771511, + 1 + ], + "ix": 4 + }, + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 9, + "s": [ + 99 + ] + }, + { + "t": 14, + "s": [ + 0 + ] + } + ], + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 1.75, + 3.988 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 18, + "st": -10, + "bm": 0 + }, + { + "ddd": 0, + "ind": 4, + "ty": 4, + "nm": "Grey Shield", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 13, + "s": [ + 100 + ] + }, + { + "t": 17, + "s": [ + 0 + ] + } + ], + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 12, + 12, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 6, + 7, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 150, + 150, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.971, + 1.298 + ], + [ + 0.042, + 1.905 + ], + [ + 0, + 1.025 + ], + [ + -0.884, + 0.155 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.003, + -0.899 + ], + [ + 0.08, + -0.839 + ], + [ + 1.13, + -1.536 + ], + [ + 1.602, + -0.257 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + -1.602, + -0.257 + ], + [ + -1.129, + -1.536 + ], + [ + -0.076, + -0.839 + ], + [ + -0.002, + -0.898 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.885, + 0.154 + ], + [ + 0, + 1.023 + ], + [ + -0.042, + 1.905 + ], + [ + -0.972, + 1.298 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 0, + 7.006 + ], + [ + -0.112, + 6.994 + ], + [ + -4.124, + 4.568 + ], + [ + -5.924, + -0.719 + ], + [ + -6, + -4.425 + ], + [ + -4.473, + -6.247 + ], + [ + 0, + -7.015 + ], + [ + 4.471, + -6.247 + ], + [ + 6, + -4.424 + ], + [ + 5.92, + -0.719 + ], + [ + 4.12, + 4.568 + ], + [ + 0.108, + 6.994 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 1, + "ty": "sh", + "ix": 2, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + -0.049, + -0.54 + ], + [ + -0.941, + -1.27 + ], + [ + -1.005, + -0.211 + ], + [ + -0.615, + 0.823 + ], + [ + -0.026, + 1.58 + ], + [ + 0, + 1.625 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 1.625 + ], + [ + 0.026, + 1.58 + ], + [ + 0.615, + 0.823 + ], + [ + 1.005, + -0.211 + ], + [ + 0.941, + -1.27 + ], + [ + 0.049, + -0.54 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -4, + -4.3 + ], + [ + -4, + -1 + ], + [ + -2.513, + 3.382 + ], + [ + 0, + 4.987 + ], + [ + 2.512, + 3.382 + ], + [ + 4, + -1 + ], + [ + 4, + -4.3 + ], + [ + 0, + -4.985 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 2", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "mm", + "mm": 1, + "nm": "Merge Paths 1", + "mn": "ADBE Vector Filter - Merge", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.501960813999, + 0.501960813999, + 0.556862771511, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 6, + 7 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 6, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 18, + "st": -10, + "bm": 0 + } + ], + "markers": [] +} \ No newline at end of file diff --git a/app/src/main/res/raw/shield_json_dark.json b/app/src/main/res/raw/shield_json_dark.json new file mode 100644 index 000000000..fed006596 --- /dev/null +++ b/app/src/main/res/raw/shield_json_dark.json @@ -0,0 +1,1582 @@ +{ + "v": "5.5.7", + "fr": 30, + "ip": 0, + "op": 17, + "w": 24, + "h": 24, + "nm": "Shield - Final 24x24 (on Dark)", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Shape Layer 1", + "td": 1, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 12, + 12, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 150, + 150, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + -0.938, + -1.062 + ], + [ + -1.312, + 0 + ], + [ + -0.812, + 1.062 + ], + [ + 0, + 0 + ], + [ + 2.188, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0.938, + 1.062 + ], + [ + 1.312, + 0 + ], + [ + 0.812, + -1.062 + ], + [ + 0, + 0 + ], + [ + -2.188, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -4.938, + -3.875 + ], + [ + -4.625, + 1.688 + ], + [ + -0.062, + 5.938 + ], + [ + 4.625, + 1.812 + ], + [ + 5, + -4.375 + ], + [ + 0, + -5.938 + ], + [ + -4.375, + -4.875 + ] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 0, + "k": 0, + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.392 + ], + "y": [ + 0 + ] + }, + "t": 0, + "s": [ + 0 + ] + }, + { + "t": 16, + "s": [ + 100 + ] + } + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0.843137015548, + 0, + 0.133333004222, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 3, + "ix": 5 + }, + "lc": 2, + "lj": 2, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Shape 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 18, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Blue Shield", + "tt": 1, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 12, + 12, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 6, + 7, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 150, + 150, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.971, + 1.298 + ], + [ + 0.042, + 1.905 + ], + [ + 0, + 1.025 + ], + [ + -0.884, + 0.155 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.003, + -0.899 + ], + [ + 0.08, + -0.839 + ], + [ + 1.13, + -1.536 + ], + [ + 1.602, + -0.257 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + -1.602, + -0.257 + ], + [ + -1.129, + -1.536 + ], + [ + -0.076, + -0.839 + ], + [ + -0.002, + -0.898 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.885, + 0.154 + ], + [ + 0, + 1.023 + ], + [ + -0.042, + 1.905 + ], + [ + -0.972, + 1.298 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 0, + 7.006 + ], + [ + -0.112, + 6.994 + ], + [ + -4.124, + 4.568 + ], + [ + -5.924, + -0.719 + ], + [ + -6, + -4.425 + ], + [ + -4.473, + -6.247 + ], + [ + 0, + -7.015 + ], + [ + 4.471, + -6.247 + ], + [ + 6, + -4.424 + ], + [ + 5.92, + -0.719 + ], + [ + 4.12, + 4.568 + ], + [ + 0.108, + 6.994 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 1, + "ty": "sh", + "ix": 2, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + -0.049, + -0.54 + ], + [ + -0.941, + -1.27 + ], + [ + -1.005, + -0.211 + ], + [ + -0.615, + 0.823 + ], + [ + -0.026, + 1.58 + ], + [ + 0, + 1.625 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 1.625 + ], + [ + 0.026, + 1.58 + ], + [ + 0.615, + 0.823 + ], + [ + 1.005, + -0.211 + ], + [ + 0.941, + -1.27 + ], + [ + 0.049, + -0.54 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -4, + -4.3 + ], + [ + -4, + -1 + ], + [ + -2.513, + 3.382 + ], + [ + 0, + 4.987 + ], + [ + 2.512, + 3.382 + ], + [ + 4, + -1 + ], + [ + 4, + -4.3 + ], + [ + 0, + -4.985 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 2", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "mm", + "mm": 1, + "nm": "Merge Paths 1", + "mn": "ADBE Vector Filter - Merge", + "hd": false + }, + { + "ty": "gf", + "o": { + "a": 0, + "k": 100, + "ix": 10 + }, + "r": 1, + "bm": 0, + "g": { + "p": 3, + "k": { + "a": 0, + "k": [ + 0, + 0.776, + 0.537, + 1, + 0.5, + 0.388, + 0.62, + 0.978, + 1, + 0, + 0.702, + 0.957 + ], + "ix": 9 + } + }, + "s": { + "a": 0, + "k": [ + 6.088, + -6.87 + ], + "ix": 5 + }, + "e": { + "a": 0, + "k": [ + -5.96, + 7.121 + ], + "ix": 6 + }, + "t": 1, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 6, + 7 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 6, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 18, + "st": -10, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Fill", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 9.75, + 12, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 1.5, + 4, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 150, + 150, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.032, + -0.345 + ], + [ + -0.755, + -1.021 + ], + [ + -0.519, + -0.174 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0.009, + 0.942 + ], + [ + -0.022, + 1.269 + ], + [ + 0.327, + 0.439 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 1.25, + -3.451 + ], + [ + -1.25, + -3.023 + ], + [ + -1.188, + -1.023 + ], + [ + -0.058, + 2.508 + ], + [ + 1.242, + 3.451 + ], + [ + 1.25, + 3.451 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "gf", + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 3, + "s": [ + 0 + ] + }, + { + "t": 13, + "s": [ + 100 + ] + } + ], + "ix": 10 + }, + "r": 1, + "bm": 0, + "g": { + "p": 3, + "k": { + "a": 0, + "k": [ + 0, + 0.776, + 0.537, + 1, + 0.5, + 0.388, + 0.62, + 0.978, + 1, + 0, + 0.702, + 0.957 + ], + "ix": 9 + } + }, + "s": { + "a": 0, + "k": [ + 7.296, + -6.978 + ], + "ix": 5 + }, + "e": { + "a": 0, + "k": [ + -4.711, + 6.943 + ], + "ix": 6 + }, + "t": 1, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-Fill", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.501960813999, + 0.501960813999, + 0.556862771511, + 1 + ], + "ix": 4 + }, + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 9, + "s": [ + 99 + ] + }, + { + "t": 14, + "s": [ + 0 + ] + } + ], + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 1.75, + 3.988 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 18, + "st": -10, + "bm": 0 + }, + { + "ddd": 0, + "ind": 4, + "ty": 4, + "nm": "Grey Shield", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 13, + "s": [ + 100 + ] + }, + { + "t": 17, + "s": [ + 0 + ] + } + ], + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 12, + 12, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 6, + 7, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 150, + 150, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.971, + 1.298 + ], + [ + 0.042, + 1.905 + ], + [ + 0, + 1.025 + ], + [ + -0.884, + 0.155 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.003, + -0.899 + ], + [ + 0.08, + -0.839 + ], + [ + 1.13, + -1.536 + ], + [ + 1.602, + -0.257 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + -1.602, + -0.257 + ], + [ + -1.129, + -1.536 + ], + [ + -0.076, + -0.839 + ], + [ + -0.002, + -0.898 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.885, + 0.154 + ], + [ + 0, + 1.023 + ], + [ + -0.042, + 1.905 + ], + [ + -0.972, + 1.298 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 0, + 7.006 + ], + [ + -0.112, + 6.994 + ], + [ + -4.124, + 4.568 + ], + [ + -5.924, + -0.719 + ], + [ + -6, + -4.425 + ], + [ + -4.473, + -6.247 + ], + [ + 0, + -7.015 + ], + [ + 4.471, + -6.247 + ], + [ + 6, + -4.424 + ], + [ + 5.92, + -0.719 + ], + [ + 4.12, + 4.568 + ], + [ + 0.108, + 6.994 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ind": 1, + "ty": "sh", + "ix": 2, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + -0.049, + -0.54 + ], + [ + -0.941, + -1.27 + ], + [ + -1.005, + -0.211 + ], + [ + -0.615, + 0.823 + ], + [ + -0.026, + 1.58 + ], + [ + 0, + 1.625 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 1.625 + ], + [ + 0.026, + 1.58 + ], + [ + 0.615, + 0.823 + ], + [ + 1.005, + -0.211 + ], + [ + 0.941, + -1.27 + ], + [ + 0.049, + -0.54 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -4, + -4.3 + ], + [ + -4, + -1 + ], + [ + -2.513, + 3.382 + ], + [ + 0, + 4.987 + ], + [ + 2.512, + 3.382 + ], + [ + 4, + -1 + ], + [ + 4, + -4.3 + ], + [ + 0, + -4.985 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 2", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "mm", + "mm": 1, + "nm": "Merge Paths 1", + "mn": "ADBE Vector Filter - Merge", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.501960813999, + 0.501960813999, + 0.556862771511, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 6, + 7 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Group 1", + "np": 6, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 18, + "st": -10, + "bm": 0 + } + ], + "markers": [] +} diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 81c15ec81..cc166103d 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -33,12 +33,25 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index aa9233787..e8a6951f6 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -34,6 +34,7 @@ 46dp + 48dp 64dp @@ -51,4 +52,7 @@ 10dp 32dp 16dp + + + 256dp diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index c1d8890f1..b7b3ba33a 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -77,10 +77,13 @@ pref_key_follow_device_theme + pref_key_etp_learn_more pref_key_tracking_protection_settings pref_key_tracking_protection pref_key_tracking_protection_exceptions - + pref_key_tracking_protection_standard + pref_key_tracking_protection_strict + pref_key_tracking_protection_onboarding pref_key_bounce_quick_action pref_key_reader_mode_notification diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 5159f8173..cfa15f038 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -54,6 +54,7 @@ @color/foundation_normal_theme @android:color/transparent @color/primary_text_normal_theme + @raw/shield_json @@ -271,6 +273,7 @@ @color/state_list_text_color 14sp 16dp + 16dp center_vertical true diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 59cff6e3c..4cdc08a04 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -57,7 +57,7 @@ + android:title="@string/preference_enhanced_tracking_protection" /> - + + + android:title="@string/preference_enhanced_tracking_protection" /> + + diff --git a/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelInteractorTest.kt new file mode 100644 index 000000000..08d571ad6 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelInteractorTest.kt @@ -0,0 +1,63 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.trackingprotection + +import io.mockk.mockk +import io.mockk.verify +import org.junit.Test + +import org.junit.Assert.assertEquals + +class TrackingProtectionPanelInteractorTest { + + @Test + fun openDetails() { + val store: TrackingProtectionStore = mockk(relaxed = true) + val interactor = + TrackingProtectionPanelInteractor(store, mockk(), mockk()) + interactor.openDetails(TrackingProtectionCategory.FINGERPRINTERS, true) + verify { + store.dispatch( + TrackingProtectionAction.EnterDetailsMode( + TrackingProtectionCategory.FINGERPRINTERS, + true + ) + ) + } + } + + @Test + fun selectTrackingProtectionSettings() { + var openSettings = false + val interactor = TrackingProtectionPanelInteractor( + mockk(), + mockk(), + { openSettings = true } + ) + interactor.selectTrackingProtectionSettings() + assertEquals(true, openSettings) + } + + @Test + fun trackingProtectionToggled() { + var trackingProtectionNewValue: Boolean? = null + val interactor = TrackingProtectionPanelInteractor( + mockk(), + { trackingProtectionNewValue = it }, + mockk() + ) + interactor.trackingProtectionToggled(true) + assertEquals(true, trackingProtectionNewValue) + } + + @Test + fun onBackPressed() { + val store: TrackingProtectionStore = mockk(relaxed = true) + val interactor = + TrackingProtectionPanelInteractor(store, mockk(), mockk()) + interactor.onBackPressed() + verify { store.dispatch(TrackingProtectionAction.ExitDetailsMode) } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStoreTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStoreTest.kt new file mode 100644 index 000000000..33c4b01c3 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStoreTest.kt @@ -0,0 +1,160 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.trackingprotection + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotSame +import kotlinx.coroutines.runBlocking +import mozilla.components.concept.engine.content.blocking.Tracker +import org.junit.Test + +class TrackingProtectionStoreTest { + @Test + fun enterDetailsMode() = runBlocking { + val initialState = defaultState() + val store = TrackingProtectionStore(initialState) + + store.dispatch( + TrackingProtectionAction.EnterDetailsMode( + TrackingProtectionCategory.FINGERPRINTERS, + true + ) + ) + .join() + assertNotSame(initialState, store.state) + assertEquals( + store.state.mode, + TrackingProtectionState.Mode.Details(TrackingProtectionCategory.FINGERPRINTERS, true) + ) + } + + @Test + fun exitDetailsMode() = runBlocking { + val initialState = detailsState() + val store = TrackingProtectionStore(initialState) + + store.dispatch(TrackingProtectionAction.ExitDetailsMode).join() + assertNotSame(initialState, store.state) + assertEquals( + store.state.mode, + TrackingProtectionState.Mode.Normal + ) + } + + @Test + fun trackerBlockingChanged() = runBlocking { + val initialState = defaultState() + val store = TrackingProtectionStore(initialState) + + store.dispatch(TrackingProtectionAction.TrackerBlockingChanged(false)).join() + assertNotSame(initialState, store.state) + assertEquals( + store.state.mode, + TrackingProtectionState.Mode.Normal + ) + assertEquals( + false, + store.state.isTrackingProtectionEnabled + ) + } + + @Test + fun trackerListChanged() = runBlocking { + val initialState = defaultState() + val store = TrackingProtectionStore(initialState) + val tracker = Tracker("url", listOf()) + + store.dispatch(TrackingProtectionAction.TrackerListChange(listOf(tracker))).join() + assertNotSame(initialState, store.state) + assertEquals( + listOf(tracker), + store.state.listTrackers + ) + } + + @Test + fun trackerLoadedListChanged() = runBlocking { + val initialState = defaultState() + val store = TrackingProtectionStore(initialState) + val tracker = Tracker("url", listOf()) + + store.dispatch(TrackingProtectionAction.TrackerLoadedListChange(listOf(tracker))).join() + assertNotSame(initialState, store.state) + assertEquals( + listOf(tracker), + store.state.listTrackersLoaded + ) + } + + @Test + fun urlChanged() = runBlocking { + val initialState = defaultState() + val store = TrackingProtectionStore(initialState) + + store.dispatch(TrackingProtectionAction.UrlChange("newURL")).join() + assertNotSame(initialState, store.state) + assertEquals( + "newURL", + store.state.url + ) + } + + @Test + fun onChange() = runBlocking { + val initialState = defaultState() + val store = TrackingProtectionStore(initialState) + val tracker = Tracker("url", listOf()) + + store.dispatch( + TrackingProtectionAction.Change( + "newURL", + false, + listOf(tracker), + listOf(), + TrackingProtectionState.Mode.Details( + TrackingProtectionCategory.FINGERPRINTERS, + true + ) + ) + ).join() + assertNotSame(initialState, store.state) + assertEquals( + "newURL", + store.state.url + ) + assertEquals( + false, + store.state.isTrackingProtectionEnabled + ) + assertEquals( + listOf(), + store.state.listTrackersLoaded + ) + assertEquals( + listOf(tracker), + store.state.listTrackers + ) + assertEquals( + TrackingProtectionState.Mode.Details(TrackingProtectionCategory.FINGERPRINTERS, true), + store.state.mode + ) + } + + private fun defaultState(): TrackingProtectionState = TrackingProtectionState( + url = "www.mozilla.org", + isTrackingProtectionEnabled = true, + listTrackers = listOf(), + listTrackersLoaded = listOf(), + mode = TrackingProtectionState.Mode.Normal + ) + + private fun detailsState(): TrackingProtectionState = TrackingProtectionState( + url = "www.mozilla.org", + isTrackingProtectionEnabled = true, + listTrackers = listOf(), + listTrackersLoaded = listOf(), + mode = TrackingProtectionState.Mode.Details(TrackingProtectionCategory.CRYPTOMINERS, true) + ) +} diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index cf35d6d07..1fc313f74 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -64,6 +64,9 @@ object Versions { const val robolectric = "4.2" const val google_ads_id_version = "16.0.0" + + const val airbnb_lottie = "3.0.7" + const val androidx_fragment_testing_version = "1.2.0-alpha02" } @@ -217,4 +220,6 @@ object Deps { const val google_ads_id = "com.google.android.gms:play-services-ads-identifier:${Versions.google_ads_id_version}" const val androidx_fragment_testing = "androidx.fragment:fragment-testing:${Versions.androidx_fragment_testing_version}" + + const val lottie = "com.airbnb.android:lottie:${Versions.airbnb_lottie}" }