For #19886 integrate view binding.

This commit is contained in:
Arturo Mejia 2021-08-16 15:34:55 -04:00
parent fd8e6a9c27
commit 512475df9e
24 changed files with 389 additions and 255 deletions

View File

@ -38,6 +38,7 @@ import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.runIfFragmentIsAttached
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.shortcut.PwaOnboardingObserver
import org.mozilla.fenix.theme.ThemeManager
@ -305,20 +306,22 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
override fun navToQuickSettingsSheet(tab: SessionState, sitePermissions: SitePermissions?) {
requireComponents.useCases.trackingProtectionUseCases.containsException(tab.id) { contains ->
val isTrackingProtectionEnabled = tab.trackingProtection.enabled && !contains
val directions =
BrowserFragmentDirections.actionBrowserFragmentToQuickSettingsSheetDialogFragment(
sessionId = tab.id,
url = tab.content.url,
title = tab.content.title,
isSecured = tab.content.securityInfo.secure,
sitePermissions = sitePermissions,
gravity = getAppropriateLayoutGravity(),
certificateName = tab.content.securityInfo.issuer,
permissionHighlights = tab.content.permissionHighlights,
isTrackingProtectionEnabled = isTrackingProtectionEnabled
)
nav(R.id.browserFragment, directions)
runIfFragmentIsAttached {
val isTrackingProtectionEnabled = tab.trackingProtection.enabled && !contains
val directions =
BrowserFragmentDirections.actionBrowserFragmentToQuickSettingsSheetDialogFragment(
sessionId = tab.id,
url = tab.content.url,
title = tab.content.title,
isSecured = tab.content.securityInfo.secure,
sitePermissions = sitePermissions,
gravity = getAppropriateLayoutGravity(),
certificateName = tab.content.securityInfo.issuer,
permissionHighlights = tab.content.permissionHighlights,
isTrackingProtectionEnabled = isTrackingProtectionEnabled
)
nav(R.id.browserFragment, directions)
}
}
}

View File

@ -127,7 +127,10 @@ class BrowserToolbarView(
hint = secondaryTextColor,
separator = separatorColor,
trackingProtection = primaryTextColor,
highlight = primaryTextColor
highlight = ContextCompat.getColor(
context,
R.color.whats_new_notification_color
)
)
display.hint = context.getString(R.string.search_hint)

View File

@ -113,12 +113,11 @@ class DefaultToolbarIntegration(
onItemTapped = {
interactor.onTabCounterMenuItemTapped(it)
},
iconColor =
if (isPrivate) {
ContextCompat.getColor(context, R.color.primary_text_private_theme)
} else {
null
}
iconColor = if (isPrivate) {
ContextCompat.getColor(context, R.color.primary_text_private_theme)
} else {
null
}
).also {
it.updateMenu(context.settings().toolbarPosition)
}

View File

@ -36,6 +36,7 @@ import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.runIfFragmentIsAttached
import org.mozilla.fenix.ext.settings
/**
@ -178,19 +179,21 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
override fun navToQuickSettingsSheet(tab: SessionState, sitePermissions: SitePermissions?) {
requireComponents.useCases.trackingProtectionUseCases.containsException(tab.id) { contains ->
val directions = ExternalAppBrowserFragmentDirections
.actionGlobalQuickSettingsSheetDialogFragment(
sessionId = tab.id,
url = tab.content.url,
title = tab.content.title,
isSecured = tab.content.securityInfo.secure,
sitePermissions = sitePermissions,
gravity = getAppropriateLayoutGravity(),
certificateName = tab.content.securityInfo.issuer,
permissionHighlights = tab.content.permissionHighlights,
isTrackingProtectionEnabled = tab.trackingProtection.enabled && !contains
)
nav(R.id.externalAppBrowserFragment, directions)
runIfFragmentIsAttached {
val directions = ExternalAppBrowserFragmentDirections
.actionGlobalQuickSettingsSheetDialogFragment(
sessionId = tab.id,
url = tab.content.url,
title = tab.content.title,
isSecured = tab.content.securityInfo.secure,
sitePermissions = sitePermissions,
gravity = getAppropriateLayoutGravity(),
certificateName = tab.content.securityInfo.issuer,
permissionHighlights = tab.content.permissionHighlights,
isTrackingProtectionEnabled = tab.trackingProtection.enabled && !contains
)
nav(R.id.externalAppBrowserFragment, directions)
}
}
}

View File

@ -0,0 +1,87 @@
/* 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.quicksettings
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.core.content.ContextCompat.getColor
import androidx.core.view.isVisible
import mozilla.components.browser.icons.BrowserIcons
import mozilla.components.support.ktx.android.content.getDrawableWithTint
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.ConnectionDetailsWebsiteInfoBinding
import org.mozilla.fenix.ext.loadIntoView
/**
* MVI View that knows to display a whether the current website connection details.
*
* Currently it does not support any user interaction.
*
* @param container [ViewGroup] in which this View will inflate itself.
* @param icons Icons component for loading, caching and processing website icons.
* @param interactor [WebSiteInfoInteractor] which will have delegated to all user interactions.
*/
class ConnectionDetailsView(
container: ViewGroup,
private val icons: BrowserIcons,
val interactor: WebSiteInfoInteractor,
) {
val binding = ConnectionDetailsWebsiteInfoBinding.inflate(
LayoutInflater.from(container.context), container, true
)
/**
* Allows changing what this View displays.
*
* @param state [WebsiteInfoState] to be rendered.
*/
fun update(state: WebsiteInfoState) {
icons.loadIntoView(binding.faviconImage, state.websiteUrl)
bindUrl(state.websiteUrl)
bindSecurityInfo(state.websiteSecurityUiValues)
bindCertificateName(state.certificateName)
bindTitle(state.websiteTitle)
bindBackButtonListener()
}
private fun bindUrl(websiteUrl: String) {
binding.url.text = websiteUrl
}
private fun bindSecurityInfo(uiValues: WebsiteSecurityUiValues) {
val tint = getColor(provideContext(), uiValues.iconTintRes)
binding.securityInfo.setText(uiValues.securityInfoRes)
binding.securityInfoIcon.setImageDrawable(
provideContext().getDrawableWithTint(uiValues.iconRes, tint)
)
}
@VisibleForTesting
internal fun provideContext(): Context = binding.root.context
@VisibleForTesting
internal fun bindBackButtonListener() {
binding.detailsBack.isVisible = true
binding.detailsBack.setOnClickListener {
interactor.onBackPressed()
}
}
@VisibleForTesting
internal fun bindTitle(websiteTitle: String) {
binding.title.text = websiteTitle
binding.titleContainer.isVisible = websiteTitle.isNotEmpty()
}
@VisibleForTesting
internal fun bindCertificateName(cert: String) {
val certificateLabel =
provideContext().getString(R.string.certificate_info_verified_by, cert)
binding.certificateInfo.text = certificateLabel
binding.certificateInfo.isVisible = cert.isNotEmpty()
}
}

View File

@ -11,22 +11,25 @@ import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.fragment_connection_details_dialog.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.state.selector.findTabOrCustomTab
import mozilla.components.browser.state.state.SessionState
import org.mozilla.fenix.R
import org.mozilla.fenix.android.FenixDialogFragment
import org.mozilla.fenix.databinding.FragmentConnectionDetailsDialogBinding
import org.mozilla.fenix.ext.requireComponents
@ExperimentalCoroutinesApi
class ConnectionPanelDialogFragment : FenixDialogFragment() {
@VisibleForTesting
private lateinit var connectionView: WebsiteInfoView
private lateinit var connectionView: ConnectionDetailsView
private val args by navArgs<ConnectionPanelDialogFragmentArgs>()
private var _binding: FragmentConnectionDetailsDialogBinding? = null
override val gravity: Int get() = args.gravity
override val layoutId: Int = R.layout.fragment_connection_details_dialog
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
@ -45,10 +48,12 @@ class ConnectionPanelDialogFragment : FenixDialogFragment() {
)
val interactor = ConnectionDetailsInteractor(controller)
connectionView = WebsiteInfoView(
container = rootView.connectionDetailsInfoLayout,
interactor = interactor,
isDetailsMode = true
_binding = FragmentConnectionDetailsDialogBinding.bind(rootView)
connectionView = ConnectionDetailsView(
binding.connectionDetailsInfoLayout,
icons = requireComponents.core.icons,
interactor = interactor
)
return rootView

View File

@ -65,9 +65,9 @@ interface QuickSettingsController {
fun handleTrackingProtectionToggled(isEnabled: Boolean)
/**
* @see [TrackingProtectionInteractor.onBlockedItemsClicked]
* @see [TrackingProtectionInteractor.onDetailsClicked]
*/
fun handleBlockedItemsClicked()
fun handleDetailsClicked()
/**
* Navigates to the connection details. Called when a user clicks on the
@ -196,7 +196,7 @@ class DefaultQuickSettingsController(
quickSettingsStore.dispatch(TrackingProtectionAction.ToggleTrackingProtectionEnabled(isEnabled))
}
override fun handleBlockedItemsClicked() {
override fun handleDetailsClicked() {
navController().popBackStack()
val state = quickSettingsStore.state.trackingProtectionState

View File

@ -32,8 +32,8 @@ class QuickSettingsInteractor(
controller.handleTrackingProtectionToggled(isEnabled)
}
override fun onBlockedItemsClicked() {
controller.handleBlockedItemsClicked()
override fun onDetailsClicked() {
controller.handleDetailsClicked()
}
override fun onConnectionDetailsClicked() {

View File

@ -20,9 +20,9 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.plus
import mozilla.components.lib.state.ext.consumeFrom
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.IntentReceiverActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.android.FenixDialogFragment
import org.mozilla.fenix.databinding.FragmentQuickSettingsDialogSheetBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.settings.PhoneFeature
@ -44,8 +44,9 @@ class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
private var tryToRequestPermissions: Boolean = false
private val args by navArgs<QuickSettingsSheetDialogFragmentArgs>()
private lateinit var binding: FragmentQuickSettingsDialogSheetBinding
private var _binding: FragmentQuickSettingsDialogSheetBinding? = null
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
override val gravity: Int get() = args.gravity
override val layoutId: Int = R.layout.fragment_quick_settings_dialog_sheet
@ -60,7 +61,7 @@ class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
val components = context.components
val rootView = inflateRootView(container)
binding = FragmentQuickSettingsDialogSheetBinding.bind(rootView)
_binding = FragmentQuickSettingsDialogSheetBinding.bind(rootView)
quickSettingsStore = QuickSettingsFragmentStore.createStore(
context = context,
@ -96,12 +97,11 @@ class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
)
interactor = QuickSettingsInteractor(quickSettingsController)
websiteInfoView = WebsiteInfoView(binding.websiteInfoLayout, interactor = interactor)
websitePermissionsView =
WebsitePermissionsView(binding.websitePermissionsLayout, interactor)
trackingProtectionView =
TrackingProtectionView(rootView.trackingProtectionLayout, interactor)
TrackingProtectionView(binding.trackingProtectionLayout, interactor)
return rootView
}
@ -148,14 +148,6 @@ class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
binding.websitePermissionsGroup.isVisible = true
}
private fun launchIntentReceiver() {
context?.let { context ->
val intent = Intent(context, IntentReceiverActivity::class.java)
intent.action = Intent.ACTION_VIEW
context.startActivity(intent)
}
}
private fun openSystemSettings() {
startActivity(
Intent().apply {

View File

@ -5,12 +5,9 @@
package org.mozilla.fenix.settings.quicksettings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.quicksettings_tracking_protection.*
import kotlinx.android.synthetic.main.switch_with_description.view.*
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.QuicksettingsTrackingProtectionBinding
import org.mozilla.fenix.trackingprotection.TrackingProtectionState
/**
@ -27,9 +24,9 @@ interface TrackingProtectionInteractor {
/**
* Navigates to the tracking protection preferences. Called when a user clicks on the
* "Blocked items" button.
* "Details" button.
*/
fun onBlockedItemsClicked()
fun onDetailsClicked()
}
/**
@ -41,29 +38,30 @@ interface TrackingProtectionInteractor {
* interactions.
*/
class TrackingProtectionView(
override val containerView: ViewGroup,
val interactor: TrackingProtectionInteractor
) : LayoutContainer {
val containerView: ViewGroup,
val interactor: TrackingProtectionInteractor,
) {
private val context = containerView.context
val view: View = LayoutInflater.from(context)
.inflate(R.layout.quicksettings_tracking_protection, containerView, true)
private val binding = QuicksettingsTrackingProtectionBinding.inflate(
LayoutInflater.from(containerView.context),
containerView,
true
)
fun update(state: TrackingProtectionState) {
bindTrackingProtectionInfo(state.isTrackingProtectionEnabled)
trackingProtectionBlockedItems.setOnClickListener {
interactor.onBlockedItemsClicked()
binding.trackingProtectionDetails.setOnClickListener {
interactor.onDetailsClicked()
}
}
private fun bindTrackingProtectionInfo(isTrackingProtectionEnabled: Boolean) {
trackingProtectionSwitch.trackingProtectionCategoryItemDescription.text =
view.context.getString(if (isTrackingProtectionEnabled) R.string.etp_panel_on else R.string.etp_panel_off)
trackingProtectionSwitch.switch_widget.isChecked = isTrackingProtectionEnabled
trackingProtectionSwitch.switch_widget.jumpDrawablesToCurrentState()
binding.trackingProtectionSwitch.trackingProtectionCategoryItemDescription.text =
context.getString(if (isTrackingProtectionEnabled) R.string.etp_panel_on else R.string.etp_panel_off)
binding.trackingProtectionSwitch.switchWidget.isChecked = isTrackingProtectionEnabled
binding.trackingProtectionSwitch.switchWidget.jumpDrawablesToCurrentState()
trackingProtectionSwitch.switch_widget.setOnCheckedChangeListener { _, isChecked ->
binding.trackingProtectionSwitch.switchWidget.setOnCheckedChangeListener { _, isChecked ->
interactor.onTrackingProtectionToggled(isChecked)
}
}

View File

@ -4,6 +4,7 @@
package org.mozilla.fenix.settings.quicksettings
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
@ -11,9 +12,9 @@ import androidx.core.content.ContextCompat.getColor
import mozilla.components.browser.icons.BrowserIcons
import mozilla.components.support.ktx.android.content.getDrawableWithTint
import mozilla.components.support.ktx.kotlin.tryGetHostFromUrl
import org.mozilla.fenix.databinding.QuicksettingsWebsiteInfoBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.loadIntoView
import org.mozilla.fenix.databinding.QuicksettingsWebsiteInfoBinding
/**
* MVI View that knows to display a whether the current website uses a secure connection or not.
@ -23,89 +24,48 @@ import org.mozilla.fenix.databinding.QuicksettingsWebsiteInfoBinding
* @param container [ViewGroup] in which this View will inflate itself.
* @param icons Icons component for loading, caching and processing website icons.
* @param interactor [WebSiteInfoInteractor] which will have delegated to all user interactions.
* @param isDetailsMode Indicates if the view should be shown in detailed mode or not.
* In normal mode only the url and connection status are visible.
* In detailed mode, the title, certificate and back button are visible,
* additionally to all the views in normal mode.
*/
class WebsiteInfoView(
container: ViewGroup,
private val icons: BrowserIcons = container.context.components.core.icons,
val interactor: WebSiteInfoInteractor,
val isDetailsMode: Boolean = false
) {
val binding = QuicksettingsWebsiteInfoBinding.inflate(
LayoutInflater.from(container.context),
container,
true
LayoutInflater.from(container.context), container, true
)
val layoutId =
if (isDetailsMode) R.layout.connection_details_website_info else R.layout.quicksettings_website_info
override val containerView: View = LayoutInflater.from(container.context)
.inflate(layoutId, container, true)
/**
* Allows changing what this View displays.
*
* @param state [WebsiteInfoState] to be rendered.
*/
fun update(state: WebsiteInfoState) {
icons.loadIntoView(binding.favicon_image, state.websiteUrl)
icons.loadIntoView(binding.faviconImage, state.websiteUrl)
bindUrl(state.websiteUrl)
bindSecurityInfo(state.websiteSecurityUiValues)
if (isDetailsMode) {
bindCertificateName(state.certificateName)
bindTitle(state.websiteTitle)
bindBackButtonListener()
}
}
private fun bindUrl(websiteUrl: String) {
url.text = if (isDetailsMode) websiteUrl else websiteUrl.tryGetHostFromUrl()
binding.url.text = websiteUrl.tryGetHostFromUrl()
}
private fun bindSecurityInfo(uiValues: WebsiteSecurityUiValues) {
val tint = getColor(containerView.context, uiValues.iconTintRes)
securityInfo.setText(uiValues.securityInfoRes)
if (!isDetailsMode) {
bindConnectionDetailsListener()
}
securityInfoIcon.setImageDrawable(
containerView.context.getDrawableWithTint(uiValues.iconRes, tint)
val tint = getColor(provideContext(), uiValues.iconTintRes)
binding.securityInfo.setText(uiValues.securityInfoRes)
bindConnectionDetailsListener()
binding.securityInfoIcon.setImageDrawable(
provideContext().getDrawableWithTint(uiValues.iconRes, tint)
)
}
@VisibleForTesting
internal fun bindConnectionDetailsListener() {
securityInfo.setOnClickListener {
binding.securityInfo.setOnClickListener {
interactor.onConnectionDetailsClicked()
}
}
@VisibleForTesting
internal fun bindBackButtonListener() {
details_back.isVisible = true
details_back.setOnClickListener {
interactor.onBackPressed()
}
}
@VisibleForTesting
internal fun bindTitle(websiteTitle: String) {
title.text = websiteTitle
if (websiteTitle.isEmpty()) {
title_container.isVisible = false
}
}
@VisibleForTesting
internal fun bindCertificateName(cert: String) {
val certificateLabel =
containerView.context.getString(R.string.certificate_info_verified_by, cert)
certificateInfo.text = certificateLabel
certificateInfo.isVisible = cert.isNotEmpty()
}
internal fun provideContext(): Context = binding.root.context
}

View File

@ -7,10 +7,11 @@ package org.mozilla.fenix.trackingprotection
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.TextView
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.SwitchCompat
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.withStyledAttributes
import kotlinx.android.synthetic.main.switch_with_description.view.*
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds
import org.mozilla.fenix.R
@ -20,6 +21,10 @@ class SwitchWithDescription @JvmOverloads constructor(
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
lateinit var switchWidget: SwitchCompat
lateinit var trackingProtectionCategoryTitle: TextView
lateinit var trackingProtectionCategoryItemDescription: TextView
init {
LayoutInflater.from(context).inflate(R.layout.switch_with_description, this, true)
@ -28,7 +33,10 @@ class SwitchWithDescription @JvmOverloads constructor(
R.styleable.SwitchWithDescription_switchIcon,
R.drawable.ic_tracking_protection
)
switch_widget.putCompoundDrawablesRelativeWithIntrinsicBounds(
switchWidget = findViewById(R.id.switch_widget)
trackingProtectionCategoryTitle = findViewById(R.id.trackingProtectionCategoryTitle)
trackingProtectionCategoryItemDescription = findViewById(R.id.trackingProtectionCategoryItemDescription)
switchWidget.putCompoundDrawablesRelativeWithIntrinsicBounds(
start = AppCompatResources.getDrawable(context, id)
)
trackingProtectionCategoryTitle.text = resources.getString(

View File

@ -36,7 +36,7 @@
android:paddingVertical="8dp">
<ImageView
android:id="@+id/favicon_image"
android:id="@+id/faviconImage"
android:layout_width="24dp"
android:layout_height="24dp"
android:importantForAccessibility="no"

View File

@ -15,14 +15,14 @@
android:layout_height="wrap_content"
android:minHeight="@dimen/tracking_protection_item_height"
android:text="@string/preference_enhanced_tracking_protection"
app:layout_constraintBottom_toTopOf="@id/trackingProtectionBlockedItems"
app:layout_constraintBottom_toTopOf="@id/trackingProtectionDetails"
app:layout_constraintTop_toTopOf="parent"
app:switchDescription="@string/etp_panel_on"
app:switchIcon="@drawable/ic_tracking_protection"
app:switchTitle="@string/preference_enhanced_tracking_protection" />
<TextView
android:id="@+id/trackingProtectionBlockedItems"
android:id="@+id/trackingProtectionDetails"
style="@style/QuickSettingsText.Icon"
android:layout_width="0dp"
android:layout_height="@dimen/quicksettings_item_height"

View File

@ -21,7 +21,7 @@
android:paddingBottom="8dp">
<ImageView
android:id="@+id/favicon_image"
android:id="@+id/faviconImage"
android:layout_width="24dp"
android:layout_height="24dp"
android:importantForAccessibility="no"

View File

@ -1267,6 +1267,8 @@
<string name="onboarding_privacy_notice_description2">Weve designed %s to give you control over what you share online and what you share with us.</string>
<!-- Text for the button to read the privacy notice -->
<string name="onboarding_privacy_notice_read_button">Read our privacy notice</string>
<!-- Content description (not visible, for screen readers etc.): Close onboarding screen -->
<string name="onboarding_close" tools:ignore="UnusedResources">Close</string>
<!-- text for the button to finish onboarding -->
<string name="onboarding_finish">Start browsing</string>
@ -1394,6 +1396,8 @@
<string name="etp_tracking_content_title">Tracking Content</string>
<!-- Description of tracking content that can be blocked by Enhanced Tracking Protection -->
<string name="etp_tracking_content_description">Stops outside ads, videos, and other content from loading that contains tracking code. May affect some website functionality.</string>
<!-- Enhanced Tracking Protection Onboarding Message shown in a dialog above the toolbar. The first parameter is the name of the application (For example: Fenix) -->
<string name="etp_onboarding_cfr_message" tools:ignore="UnusedResources">Every time the shield is purple, %s has blocked trackers on a site. Tap for more info.</string>
<!-- Enhanced Tracking Protection message that protection is currently on for this site -->
<string name="etp_panel_on">Protections are ON for this site</string>
<!-- Enhanced Tracking Protection message that protection is currently off for this site -->

View File

@ -0,0 +1,104 @@
/* 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.quicksettings
import android.widget.FrameLayout
import androidx.core.view.isVisible
import io.mockk.every
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import mozilla.components.browser.icons.BrowserIcons
import mozilla.components.browser.icons.IconRequest
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.databinding.ConnectionDetailsWebsiteInfoBinding
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
class ConnectionDetailsViewTest {
private lateinit var view: ConnectionDetailsView
private lateinit var icons: BrowserIcons
private lateinit var binding: ConnectionDetailsWebsiteInfoBinding
private lateinit var interactor: WebSiteInfoInteractor
@Before
fun setup() {
icons = mockk(relaxed = true)
interactor = mockk(relaxed = true)
view = spyk(ConnectionDetailsView(FrameLayout(testContext), icons, interactor))
binding = view.binding
every { icons.loadIntoView(any(), any()) } returns mockk()
}
@Test
fun `WHEN updating THEN bind url and title`() {
val websiteUrl = "https://mozilla.org"
view.update(
WebsiteInfoState(
websiteUrl = websiteUrl,
websiteTitle = "Mozilla",
websiteSecurityUiValues = WebsiteSecurityUiValues.SECURE,
certificateName = ""
)
)
verify { icons.loadIntoView(binding.faviconImage, IconRequest(websiteUrl)) }
assertEquals("https://mozilla.org", binding.url.text)
assertEquals("Secure Connection", binding.securityInfo.text)
}
@Test
fun `WHEN updating THEN bind certificate`() {
view.update(
WebsiteInfoState(
websiteUrl = "https://mozilla.org",
websiteTitle = "Mozilla",
websiteSecurityUiValues = WebsiteSecurityUiValues.INSECURE,
certificateName = "Certificate"
)
)
assertEquals("Insecure Connection", binding.securityInfo.text)
}
@Test
fun `WHEN updating THEN bind the certificate, title and back button listener`() {
view.update(
WebsiteInfoState(
websiteUrl = "https://mozilla.org",
websiteTitle = "Mozilla",
websiteSecurityUiValues = WebsiteSecurityUiValues.INSECURE,
certificateName = "Certificate"
)
)
verify {
view.bindCertificateName("Certificate")
view.bindTitle("Mozilla")
view.bindBackButtonListener()
}
}
@Test
fun `WHEN title is empty THEN the title should be gone`() {
view.bindTitle("")
assertFalse(binding.titleContainer.isVisible)
view.bindTitle("Title")
assertTrue(binding.titleContainer.isVisible)
}
}

View File

@ -106,8 +106,8 @@ class DefaultQuickSettingsControllerTest {
reload = reload,
addNewTab = addNewTab,
requestRuntimePermissions = requestPermissions,
displayPermissions = displayPermissions,
dismiss = dismiss
displayPermissions = {},
dismiss = {}
)
)
}
@ -184,7 +184,7 @@ class DefaultQuickSettingsControllerTest {
addNewTab = addNewTab,
requestRuntimePermissions = requestPermissions,
displayPermissions = {},
dismiss = dismiss
dismiss = {}
)
every { websitePermission.phoneFeature } returns PhoneFeature.CAMERA
@ -196,7 +196,9 @@ class DefaultQuickSettingsControllerTest {
verify {
navController.navigate(
directionsEq(
QuickSettingsSheetDialogFragmentDirections.actionGlobalSitePermissionsManagePhoneFeature(PhoneFeature.CAMERA)
QuickSettingsSheetDialogFragmentDirections.actionGlobalSitePermissionsManagePhoneFeature(
PhoneFeature.CAMERA
)
)
)
}
@ -235,11 +237,13 @@ class DefaultQuickSettingsControllerTest {
store.dispatch(any())
}
}
@Test
fun `handleAndroidPermissionGranted should update the View's state`() {
val featureGranted = PhoneFeature.CAMERA
val permissionStatus = featureGranted.getActionLabel(context, sitePermissions, appSettings)
val permissionEnabled = featureGranted.shouldBeEnabled(context, sitePermissions, appSettings)
val permissionEnabled =
featureGranted.shouldBeEnabled(context, sitePermissions, appSettings)
every { store.dispatch(any()) } returns mockk()
controller.handleAndroidPermissionGranted(featureGranted)
@ -272,53 +276,32 @@ class DefaultQuickSettingsControllerTest {
}
@Test
fun `handlePermissionsChange should store the updated permission and reload webpage`() = coroutinesScope.runBlockingTest {
val testPermissions = mockk<SitePermissions>()
fun `handlePermissionsChange should store the updated permission and reload webpage`() =
coroutinesScope.runBlockingTest {
val testPermissions = mockk<SitePermissions>()
controller.handlePermissionsChange(testPermissions)
advanceUntilIdle()
controller.handlePermissionsChange(testPermissions)
advanceUntilIdle()
coVerifyOrder {
permissionStorage.updateSitePermissions(testPermissions)
reload(tab.id)
coVerifyOrder {
permissionStorage.updateSitePermissions(testPermissions)
reload(tab.id)
}
}
}
@Test
fun `handleAutoplayAdd should store the updated permission and reload webpage`() = coroutinesScope.runBlockingTest {
val testPermissions = mockk<SitePermissions>()
fun `handleAutoplayAdd should store the updated permission and reload webpage`() =
coroutinesScope.runBlockingTest {
val testPermissions = mockk<SitePermissions>()
controller.handleAutoplayAdd(testPermissions)
advanceUntilIdle()
controller.handleAutoplayAdd(testPermissions)
advanceUntilIdle()
coVerifyOrder {
permissionStorage.add(testPermissions)
reload(tab.id)
coVerifyOrder {
permissionStorage.add(testPermissions)
reload(tab.id)
}
}
}
private fun createController(
requestPermissions: (Array<String>) -> Unit = { _ -> },
displayPermissions: () -> Unit = { },
dismiss: () -> Unit = { }
): DefaultQuickSettingsController {
return DefaultQuickSettingsController(
context = context,
quickSettingsStore = store,
browserStore = browserStore,
sessionId = tab.id,
ioScope = coroutinesScope,
navController = navController,
sitePermissions = sitePermissions,
settings = appSettings,
permissionStorage = permissionStorage,
reload = reload,
addNewTab = addNewTab,
requestRuntimePermissions = requestPermissions,
displayPermissions = displayPermissions,
dismiss = dismiss
)
}
@Test
fun `handleTrackingProtectionToggled should call the right use cases`() {
@ -370,7 +353,7 @@ class DefaultQuickSettingsControllerTest {
every { store.state.trackingProtectionState } returns state
controller.handleBlockedItemsClicked()
controller.handleDetailsClicked()
verify {
navController.popBackStack()
@ -402,4 +385,29 @@ class DefaultQuickSettingsControllerTest {
navController.navigate(any<NavDirections>())
}
}
private fun createController(
requestPermissions: (Array<String>) -> Unit = { _ -> },
displayPermissions: () -> Unit = { },
dismiss: () -> Unit = { }
): DefaultQuickSettingsController {
return spyk(
DefaultQuickSettingsController(
context = context,
quickSettingsStore = store,
browserStore = browserStore,
sessionId = tab.id,
ioScope = coroutinesScope,
navController = { navController },
sitePermissions = sitePermissions,
settings = appSettings,
permissionStorage = permissionStorage,
reload = reload,
addNewTab = addNewTab,
requestRuntimePermissions = requestPermissions,
displayPermissions = displayPermissions,
dismiss = dismiss
)
)
}
}

View File

@ -6,7 +6,6 @@ package org.mozilla.fenix.settings.quicksettings
import kotlinx.coroutines.runBlocking
import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.support.test.mock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotSame
@ -14,6 +13,7 @@ import org.junit.Assert.assertTrue
import org.junit.Test
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.trackingprotection.TrackingProtectionState
import org.mozilla.fenix.trackingprotection.TrackingProtectionState.Mode.Normal
class QuickSettingsFragmentReducerTest {
@ -30,7 +30,11 @@ class QuickSettingsFragmentReducerTest {
val map =
mapOf<PhoneFeature, WebsitePermission>(PhoneFeature.CAMERA to toggleablePermission)
val infoState = WebsiteInfoState("", "", WebsiteSecurityUiValues.SECURE, "")
val state = QuickSettingsFragmentState(infoState, map)
val tpState = TrackingProtectionState(
null, "", false, emptyList(),
Normal, ""
)
val state = QuickSettingsFragmentState(infoState, map, tpState)
val newState = quickSettingsFragmentReducer(
state,
WebsitePermissionAction.TogglePermission(
@ -59,7 +63,11 @@ class QuickSettingsFragmentReducerTest {
val map =
mapOf<PhoneFeature, WebsitePermission>(PhoneFeature.AUTOPLAY to permissionPermission)
val infoState = WebsiteInfoState("", "", WebsiteSecurityUiValues.SECURE, "")
val state = QuickSettingsFragmentState(infoState, map)
val tpState = TrackingProtectionState(
null, "", false, emptyList(),
Normal, ""
)
val state = QuickSettingsFragmentState(infoState, map, tpState)
val autoplayValue = AutoplayValue.AllowAll(
label = "newLabel",
rules = createTestRule(),
@ -78,14 +86,14 @@ class QuickSettingsFragmentReducerTest {
@Test
fun `TrackingProtectionAction - ToggleTrackingProtectionEnabled`() = runBlocking {
val state = QuickSettingsFragmentState(
webInfoState = mock(),
websitePermissionsState = mock(),
webInfoState = WebsiteInfoState("", "", WebsiteSecurityUiValues.SECURE, ""),
websitePermissionsState = emptyMap(),
trackingProtectionState = TrackingProtectionState(
tab = null,
url = "https://www.firefox.com",
isTrackingProtectionEnabled = true,
listTrackers = listOf(),
mode = TrackingProtectionState.Mode.Normal,
mode = Normal,
lastAccessedCategory = ""
)
)

View File

@ -67,10 +67,10 @@ class QuickSettingsInteractorTest {
@Test
fun `onBlockedItemsClicked should delegate the controller`() {
interactor.onBlockedItemsClicked()
interactor.onDetailsClicked()
verify {
controller.handleBlockedItemsClicked()
controller.handleDetailsClicked()
}
}

View File

@ -16,7 +16,6 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.QuicksettingsWebsiteInfoBinding
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@ -32,30 +31,32 @@ class WebsiteInfoViewTest {
fun setup() {
icons = mockk(relaxed = true)
interactor = mockk(relaxed = true)
view = WebsiteInfoView(FrameLayout(testContext), icons, interactor)
view = spyk(WebsiteInfoView(FrameLayout(testContext), icons, interactor))
binding = view.binding
every { icons.loadIntoView(view.favicon_image, any()) } returns mockk()
every { icons.loadIntoView(any(), any()) } returns mockk()
}
@Test
fun bindUrlAndTitle() {
fun `WHEN updating THEN bind url`() {
val websiteUrl = "https://mozilla.org"
view.update(WebsiteInfoState(
websiteUrl = websiteUrl,
websiteTitle = "Mozilla",
websiteSecurityUiValues = WebsiteSecurityUiValues.SECURE,
certificateName = ""
))
view.update(
WebsiteInfoState(
websiteUrl = websiteUrl,
websiteTitle = "Mozilla",
websiteSecurityUiValues = WebsiteSecurityUiValues.SECURE,
certificateName = ""
)
)
verify { icons.loadIntoView(binding..favicon_image, IconRequest(websiteUrl)) }
verify { icons.loadIntoView(binding.faviconImage, IconRequest(websiteUrl)) }
assertEquals("mozilla.org", binding.url.text)
assertEquals("Secure Connection", binding.securityInfo.text)
}
@Test
fun bindCert() {
fun `WHEN updating THEN bind certificate`() {
view.update(
WebsiteInfoState(
websiteUrl = "https://mozilla.org",
@ -65,57 +66,8 @@ class WebsiteInfoViewTest {
)
)
verify { view.bindConnectionDetailsListener() }
assertEquals("Insecure Connection", binding.securityInfo.text)
}
@Test
fun `WHEN updating on detailed mode THEN bind the certificate, title and back button listener`() {
val view = spyk(WebsiteInfoView(FrameLayout(testContext), icons, interactor, isDetailsMode = true))
view.update(WebsiteInfoState(
websiteUrl = "https://mozilla.org",
websiteTitle = "Mozilla",
websiteSecurityUiValues = WebsiteSecurityUiValues.INSECURE,
certificateName = "Certificate"
))
verify {
view.bindCertificateName("Certificate")
view.bindTitle("Mozilla")
view.bindBackButtonListener()
}
}
@Test
fun `WHEN updating on not detailed mode THEN only connection details listener should be binded`() {
val view = spyk(WebsiteInfoView(FrameLayout(testContext), icons, interactor, isDetailsMode = false))
view.update(WebsiteInfoState(
websiteUrl = "https://mozilla.org",
websiteTitle = "Mozilla",
websiteSecurityUiValues = WebsiteSecurityUiValues.INSECURE,
certificateName = "Certificate"
))
verify(exactly = 0) {
view.bindCertificateName("Certificate")
view.bindTitle("Mozilla")
view.bindBackButtonListener()
}
verify {
view.bindConnectionDetailsListener()
}
}
@Test
fun `WHEN rendering THEN use the correct layout`() {
val normalView = WebsiteInfoView(FrameLayout(testContext), icons, interactor, isDetailsMode = false)
assertEquals(R.layout.quicksettings_website_info, normalView.layoutId)
val detailedView = WebsiteInfoView(FrameLayout(testContext), icons, interactor, isDetailsMode = true)
assertEquals(R.layout.connection_details_website_info, detailedView.layoutId)
}
}

View File

@ -85,7 +85,7 @@ class TrackingProtectionPanelInteractorTest {
@Test
fun `WHEN openDetails is called THEN store should dispatch EnterDetailsMode action with the right category`() {
interactor.openDetails(TrackingProtectionCategory.FINGERPRINTERS, true, {}, {})
interactor.openDetails(TrackingProtectionCategory.FINGERPRINTERS, true)
verify {
store.dispatch(