For #19886 - Add connection sub-menu.
This commit is contained in:
parent
63368779df
commit
07bb1113f8
|
@ -0,0 +1,81 @@
|
|||
/* 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.android
|
||||
|
||||
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 com.google.android.material.R
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
|
||||
/**
|
||||
* Base [AppCompatDialogFragment] that adds behaviour to create a top or bottom dialog.
|
||||
*/
|
||||
abstract class FenixDialogFragment : AppCompatDialogFragment() {
|
||||
/**
|
||||
* Indicates the position of the dialog top or bottom.
|
||||
*/
|
||||
abstract val gravity: Int
|
||||
/**
|
||||
* The layout id that will be render on the dialog.
|
||||
*/
|
||||
abstract val layoutId: Int
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return if (gravity == Gravity.BOTTOM) {
|
||||
BottomSheetDialog(requireContext(), this.theme).apply {
|
||||
setOnShowListener {
|
||||
val bottomSheet =
|
||||
findViewById<View>(R.id.design_bottom_sheet) as FrameLayout
|
||||
val behavior = BottomSheetBehavior.from(bottomSheet)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Dialog(requireContext()).applyCustomizationsForTopDialog(inflateRootView())
|
||||
}
|
||||
}
|
||||
|
||||
private fun Dialog.applyCustomizationsForTopDialog(rootView: View): Dialog {
|
||||
addContentView(
|
||||
rootView,
|
||||
LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
)
|
||||
|
||||
window?.apply {
|
||||
setGravity(gravity)
|
||||
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
|
||||
}
|
||||
|
||||
fun inflateRootView(container: ViewGroup? = null): View {
|
||||
val contextThemeWrapper = ContextThemeWrapper(
|
||||
activity,
|
||||
(activity as HomeActivity).themeManager.currentThemeResource
|
||||
)
|
||||
return LayoutInflater.from(contextThemeWrapper).inflate(
|
||||
layoutId,
|
||||
container,
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
|
@ -127,10 +127,7 @@ class BrowserToolbarView(
|
|||
hint = secondaryTextColor,
|
||||
separator = separatorColor,
|
||||
trackingProtection = primaryTextColor,
|
||||
highlight = ContextCompat.getColor(
|
||||
context,
|
||||
R.color.whats_new_notification_color
|
||||
)
|
||||
highlight = primaryTextColor
|
||||
)
|
||||
|
||||
display.hint = context.getString(R.string.search_hint)
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/* 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 androidx.fragment.app.Fragment
|
||||
import androidx.navigation.NavController
|
||||
import mozilla.components.browser.state.state.SessionState
|
||||
import mozilla.components.concept.engine.permission.SitePermissions
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.browser.BrowserFragmentDirections
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.nav
|
||||
import org.mozilla.fenix.ext.runIfFragmentIsAttached
|
||||
|
||||
/**
|
||||
* [ConnectionDetailsController] controller.
|
||||
*
|
||||
* Delegated by View Interactors, handles container business logic and operates changes on it,
|
||||
* complex Android interactions or communication with other features.
|
||||
*/
|
||||
interface ConnectionDetailsController {
|
||||
/**
|
||||
* @see [WebSiteInfoInteractor.onBackPressed]
|
||||
*/
|
||||
fun handleBackPressed()
|
||||
}
|
||||
|
||||
/**
|
||||
* Default behavior of [ConnectionDetailsController].
|
||||
*
|
||||
* @param dismiss callback allowing to request this entire Fragment to be dismissed.
|
||||
*/
|
||||
class DefaultConnectionDetailsController(
|
||||
private val context: Context,
|
||||
private val fragment: Fragment,
|
||||
private val navController: NavController,
|
||||
internal var sitePermissions: SitePermissions?,
|
||||
private val gravity: Int,
|
||||
private val getCurrentTab: () -> SessionState?,
|
||||
private val dismiss: () -> Unit
|
||||
) : ConnectionDetailsController {
|
||||
override fun handleBackPressed() {
|
||||
getCurrentTab()?.let { tab ->
|
||||
context.components.useCases.trackingProtectionUseCases.containsException(tab.id) { contains ->
|
||||
fragment.runIfFragmentIsAttached {
|
||||
dismiss()
|
||||
val isTrackingProtectionEnabled = tab.trackingProtection.enabled && !contains
|
||||
val directions =
|
||||
BrowserFragmentDirections.actionGlobalQuickSettingsSheetDialogFragment(
|
||||
sessionId = tab.id,
|
||||
url = tab.content.url,
|
||||
title = tab.content.title,
|
||||
isSecured = tab.content.securityInfo.secure,
|
||||
sitePermissions = sitePermissions,
|
||||
gravity = gravity,
|
||||
certificateName = tab.content.securityInfo.issuer,
|
||||
permissionHighlights = tab.content.permissionHighlights,
|
||||
isTrackingProtectionEnabled = isTrackingProtectionEnabled
|
||||
)
|
||||
navController.nav(R.id.quickSettingsSheetDialogFragment, directions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/* 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
|
||||
|
||||
/**
|
||||
* [ConnectionPanelDialogFragment] interactor.
|
||||
*
|
||||
* Implements callbacks for each of [ConnectionPanelDialogFragment]'s Views declared possible user interactions,
|
||||
* delegates all such user events to the [ConnectionDetailsController].
|
||||
*
|
||||
* @param controller [ConnectionDetailsController] which will be delegated for all users interactions,
|
||||
* it expected to contain all business logic for how to act in response.
|
||||
*/
|
||||
class ConnectionDetailsInteractor(
|
||||
private val controller: ConnectionDetailsController
|
||||
) : WebSiteInfoInteractor {
|
||||
|
||||
override fun onBackPressed() {
|
||||
controller.handleBackPressed()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/* 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.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
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.ext.requireComponents
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class ConnectionPanelDialogFragment : FenixDialogFragment() {
|
||||
@VisibleForTesting
|
||||
private lateinit var connectionView: WebsiteInfoView
|
||||
private val args by navArgs<ConnectionPanelDialogFragmentArgs>()
|
||||
|
||||
override val gravity: Int get() = args.gravity
|
||||
override val layoutId: Int = R.layout.fragment_connection_details_dialog
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val rootView = inflateRootView(container)
|
||||
val controller = DefaultConnectionDetailsController(
|
||||
context = requireContext(),
|
||||
fragment = this,
|
||||
navController = findNavController(),
|
||||
sitePermissions = args.sitePermissions,
|
||||
gravity = args.gravity,
|
||||
getCurrentTab = ::getCurrentTab,
|
||||
dismiss = ::dismiss
|
||||
)
|
||||
val interactor = ConnectionDetailsInteractor(controller)
|
||||
connectionView = WebsiteInfoView(
|
||||
container = rootView.connectionDetailsInfoLayout,
|
||||
interactor = interactor,
|
||||
isDetailsMode = true
|
||||
)
|
||||
|
||||
return rootView
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
connectionView.update(
|
||||
WebsiteInfoState.createWebsiteInfoState(
|
||||
args.url,
|
||||
args.title,
|
||||
args.isSecured,
|
||||
args.certificateName
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
internal fun getCurrentTab(): SessionState? {
|
||||
return requireComponents.core.store.state.findTabOrCustomTab(args.sessionId)
|
||||
}
|
||||
}
|
|
@ -69,6 +69,12 @@ interface QuickSettingsController {
|
|||
* @see [TrackingProtectionInteractor.onBlockedItemsClicked]
|
||||
*/
|
||||
fun handleBlockedItemsClicked()
|
||||
|
||||
/**
|
||||
* Navigates to the connection details. Called when a user clicks on the
|
||||
* "Secured or Insecure Connection" section.
|
||||
*/
|
||||
fun handleConnectionDetailsClicked()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -205,6 +211,23 @@ class DefaultQuickSettingsController(
|
|||
navController.nav(R.id.quickSettingsSheetDialogFragment, directions)
|
||||
}
|
||||
|
||||
override fun handleConnectionDetailsClicked() {
|
||||
dismiss.invoke()
|
||||
|
||||
val state = quickSettingsStore.state.webInfoState
|
||||
val directions = ConnectionPanelDialogFragmentDirections
|
||||
.actionGlobalConnectionDetailsDialogFragment(
|
||||
sessionId = sessionId,
|
||||
title = state.websiteTitle,
|
||||
url = state.websiteUrl,
|
||||
isSecured = state.websiteSecurityUiValues == WebsiteSecurityUiValues.SECURE,
|
||||
certificateName = state.certificateName,
|
||||
gravity = context.components.settings.toolbarPosition.androidGravity,
|
||||
sitePermissions = sitePermissions
|
||||
)
|
||||
navController.nav(R.id.quickSettingsSheetDialogFragment, directions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a certain set of runtime Android permissions.
|
||||
*
|
||||
|
|
|
@ -41,7 +41,31 @@ data class WebsiteInfoState(
|
|||
val websiteTitle: String,
|
||||
val websiteSecurityUiValues: WebsiteSecurityUiValues,
|
||||
val certificateName: String
|
||||
) : State
|
||||
) : State {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Construct an initial [WebsiteInfoState]
|
||||
* based on the current website's status and connection.
|
||||
* While being displayed users have no way of modifying it.
|
||||
*
|
||||
* @param websiteUrl [String] the URL of the current web page.
|
||||
* @param websiteTitle [String] the title of the current web page.
|
||||
* @param isSecured [Boolean] whether the connection is secured (TLS) or not.
|
||||
* @param certificateName [String] the certificate name of the current web page.
|
||||
*/
|
||||
fun createWebsiteInfoState(
|
||||
websiteUrl: String,
|
||||
websiteTitle: String,
|
||||
isSecured: Boolean,
|
||||
certificateName: String
|
||||
): WebsiteInfoState {
|
||||
val uiValues =
|
||||
if (isSecured) WebsiteSecurityUiValues.SECURE else WebsiteSecurityUiValues.INSECURE
|
||||
return WebsiteInfoState(websiteUrl, websiteTitle, uiValues, certificateName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class WebsiteSecurityUiValues(
|
||||
@StringRes val securityInfoRes: Int,
|
||||
|
@ -55,7 +79,7 @@ enum class WebsiteSecurityUiValues(
|
|||
),
|
||||
INSECURE(
|
||||
R.string.quick_settings_sheet_insecure_connection,
|
||||
R.drawable.mozac_ic_globe,
|
||||
R.drawable.mozac_ic_broken_lock,
|
||||
R.color.photonRed50
|
||||
)
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import mozilla.components.lib.state.Store
|
|||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.settings.PhoneFeature
|
||||
import org.mozilla.fenix.settings.quicksettings.QuickSettingsFragmentStore.Companion.createStore
|
||||
import org.mozilla.fenix.settings.quicksettings.WebsiteInfoState.Companion.createWebsiteInfoState
|
||||
import org.mozilla.fenix.settings.quicksettings.ext.shouldBeEnabled
|
||||
import org.mozilla.fenix.settings.quicksettings.ext.shouldBeVisible
|
||||
import org.mozilla.fenix.trackingprotection.TrackingProtectionState
|
||||
|
@ -91,26 +92,6 @@ class QuickSettingsFragmentStore(
|
|||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* Construct an initial [WebsiteInfoState] to be rendered by [WebsiteInfoView]
|
||||
* based on the current website's status and connection.
|
||||
*
|
||||
* While being displayed users have no way of modifying it.
|
||||
*
|
||||
* @param websiteUrl [String] the URL of the current web page.
|
||||
* @param isSecured [Boolean] whether the connection is secured (TLS) or not.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
fun createWebsiteInfoState(
|
||||
websiteUrl: String,
|
||||
websiteTitle: String,
|
||||
isSecured: Boolean,
|
||||
certificateName: String
|
||||
): WebsiteInfoState {
|
||||
val uiValues = if (isSecured) WebsiteSecurityUiValues.SECURE else WebsiteSecurityUiValues.INSECURE
|
||||
return WebsiteInfoState(websiteUrl, websiteTitle, uiValues, certificateName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an initial [WebsitePermissions
|
||||
* State] to be rendered by [WebsitePermissionsView]
|
||||
|
|
|
@ -15,7 +15,7 @@ package org.mozilla.fenix.settings.quicksettings
|
|||
*/
|
||||
class QuickSettingsInteractor(
|
||||
private val controller: QuickSettingsController
|
||||
) : WebsitePermissionInteractor, TrackingProtectionInteractor {
|
||||
) : WebsitePermissionInteractor, TrackingProtectionInteractor, WebSiteInfoInteractor {
|
||||
override fun onPermissionsShown() {
|
||||
controller.handlePermissionsShown()
|
||||
}
|
||||
|
@ -35,4 +35,8 @@ class QuickSettingsInteractor(
|
|||
override fun onBlockedItemsClicked() {
|
||||
controller.handleBlockedItemsClicked()
|
||||
}
|
||||
|
||||
override fun onConnectionDetailsClicked() {
|
||||
controller.handleConnectionDetailsClicked()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,39 +4,27 @@
|
|||
|
||||
package org.mozilla.fenix.settings.quicksettings
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.Gravity.BOTTOM
|
||||
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.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.plus
|
||||
import mozilla.components.lib.state.ext.consumeFrom
|
||||
import org.mozilla.fenix.BuildConfig
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.IntentReceiverActivity
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.databinding.FragmentQuickSettingsDialogSheetBinding
|
||||
import org.mozilla.fenix.android.FenixDialogFragment
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.settings.PhoneFeature
|
||||
import com.google.android.material.R as MaterialR
|
||||
|
||||
/**
|
||||
* Dialog that presents the user with information about
|
||||
|
@ -44,7 +32,7 @@ import com.google.android.material.R as MaterialR
|
|||
* - website tracking protection.
|
||||
* - website permission.
|
||||
*/
|
||||
class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
|
||||
class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
|
||||
|
||||
private lateinit var quickSettingsStore: QuickSettingsFragmentStore
|
||||
private lateinit var quickSettingsController: QuickSettingsController
|
||||
|
@ -58,6 +46,9 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
|
|||
|
||||
private lateinit var binding: FragmentQuickSettingsDialogSheetBinding
|
||||
|
||||
override val gravity: Int get() = args.gravity
|
||||
override val layoutId: Int = R.layout.fragment_quick_settings_dialog_sheet
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
// https://github.com/mozilla-mobile/fenix/issues/19920
|
||||
override fun onCreateView(
|
||||
|
@ -106,7 +97,7 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
|
|||
|
||||
interactor = QuickSettingsInteractor(quickSettingsController)
|
||||
|
||||
websiteInfoView = WebsiteInfoView(binding.websiteInfoLayout)
|
||||
websiteInfoView = WebsiteInfoView(binding.websiteInfoLayout, interactor = interactor)
|
||||
websitePermissionsView =
|
||||
WebsitePermissionsView(binding.websitePermissionsLayout, interactor)
|
||||
trackingProtectionView =
|
||||
|
@ -115,33 +106,6 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
|
|||
return rootView
|
||||
}
|
||||
|
||||
private fun inflateRootView(container: ViewGroup? = null): View {
|
||||
val contextThemeWrapper = ContextThemeWrapper(
|
||||
activity,
|
||||
(activity as HomeActivity).themeManager.currentThemeResource
|
||||
)
|
||||
return LayoutInflater.from(contextThemeWrapper).inflate(
|
||||
R.layout.fragment_quick_settings_dialog_sheet,
|
||||
container,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return if (args.gravity == BOTTOM) {
|
||||
BottomSheetDialog(requireContext(), this.theme).apply {
|
||||
setOnShowListener {
|
||||
val bottomSheet =
|
||||
findViewById<View>(MaterialR.id.design_bottom_sheet) as FrameLayout
|
||||
val behavior = BottomSheetBehavior.from(bottomSheet)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Dialog(requireContext()).applyCustomizationsForTopDialog(inflateRootView())
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
@ -177,24 +141,6 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
|
|||
tryToRequestPermissions = false
|
||||
}
|
||||
|
||||
private fun Dialog.applyCustomizationsForTopDialog(rootView: View): Dialog {
|
||||
addContentView(
|
||||
rootView,
|
||||
LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
)
|
||||
|
||||
window?.apply {
|
||||
setGravity(args.gravity)
|
||||
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
|
||||
}
|
||||
|
||||
private fun arePermissionsGranted(requestCode: Int, grantResults: IntArray) =
|
||||
requestCode == REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS && grantResults.all { it == PERMISSION_GRANTED }
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/* 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
|
||||
|
||||
/**
|
||||
* Contract declaring all possible user interactions with [WebsitePermissionsView].
|
||||
*/
|
||||
interface WebSiteInfoInteractor {
|
||||
/**
|
||||
* Indicates there are website permissions allowed / blocked for the current website.
|
||||
* which, status which is shown to the user.
|
||||
*/
|
||||
fun onConnectionDetailsClicked() = Unit
|
||||
|
||||
/**
|
||||
* Called whenever back is pressed.
|
||||
*/
|
||||
fun onBackPressed() = Unit
|
||||
}
|
|
@ -6,6 +6,7 @@ package org.mozilla.fenix.settings.quicksettings
|
|||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.content.ContextCompat.getColor
|
||||
import mozilla.components.browser.icons.BrowserIcons
|
||||
import mozilla.components.support.ktx.android.content.getDrawableWithTint
|
||||
|
@ -20,18 +21,32 @@ import org.mozilla.fenix.databinding.QuicksettingsWebsiteInfoBinding
|
|||
* Currently it does not support any user interaction.
|
||||
*
|
||||
* @param container [ViewGroup] in which this View will inflate itself.
|
||||
* @param icons [BrowserIcons] instance for rendering the sites icon.
|
||||
* @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
|
||||
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
|
||||
)
|
||||
|
||||
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.
|
||||
*
|
||||
|
@ -41,17 +56,56 @@ class WebsiteInfoView(
|
|||
icons.loadIntoView(binding.favicon_image, state.websiteUrl)
|
||||
bindUrl(state.websiteUrl)
|
||||
bindSecurityInfo(state.websiteSecurityUiValues)
|
||||
if (isDetailsMode) {
|
||||
bindCertificateName(state.certificateName)
|
||||
bindTitle(state.websiteTitle)
|
||||
bindBackButtonListener()
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindUrl(websiteUrl: String) {
|
||||
binding.url.text = websiteUrl.tryGetHostFromUrl()
|
||||
url.text = if (isDetailsMode) websiteUrl else websiteUrl.tryGetHostFromUrl()
|
||||
}
|
||||
|
||||
private fun bindSecurityInfo(uiValues: WebsiteSecurityUiValues) {
|
||||
val tint = getColor(binding.root.context, uiValues.iconTintRes)
|
||||
binding.securityInfo.setText(uiValues.securityInfoRes)
|
||||
binding.securityInfoIcon.setImageDrawable(
|
||||
binding.root.context.getDrawableWithTint(uiValues.iconRes, tint)
|
||||
val tint = getColor(containerView.context, uiValues.iconTintRes)
|
||||
securityInfo.setText(uiValues.securityInfoRes)
|
||||
if (!isDetailsMode) {
|
||||
bindConnectionDetailsListener()
|
||||
}
|
||||
securityInfoIcon.setImageDrawable(
|
||||
containerView.context.getDrawableWithTint(uiValues.iconRes, tint)
|
||||
)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun bindConnectionDetailsListener() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/website_info_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?foundation"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/details_back"
|
||||
android:layout_width="@dimen/quicksettings_item_height"
|
||||
android:layout_height="@dimen/quicksettings_item_height"
|
||||
android:scaleType="center"
|
||||
android:contentDescription="@string/etp_back_button_content_description"
|
||||
app:tint="?attr/primaryText"
|
||||
app:srcCompat="@drawable/mozac_ic_back" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/title_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:minHeight="@dimen/tracking_protection_item_height"
|
||||
android:paddingVertical="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/favicon_image"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:scaleType="fitCenter"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
tools:drawable="@drawable/ic_internet" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
style="@style/QuickSettingsText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingStart="8dp"
|
||||
android:textStyle="bold"
|
||||
tools:text="Wikipedia"
|
||||
tools:ignore="RtlSymmetry" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/url"
|
||||
style="@style/QuickSettingsText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="8dp"
|
||||
tools:text="https://wikipedia.org" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="@dimen/tracking_protection_item_height"
|
||||
android:orientation="horizontal"
|
||||
style="@style/QuickSettingsText"
|
||||
android:paddingVertical="8dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/securityInfoIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAccessibility="no"
|
||||
app:srcCompat="@drawable/mozac_ic_lock"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/securityInfo"
|
||||
style="@style/QuickSettingsLargeText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="0dp"
|
||||
tools:text="Connection is secure" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/certificateInfo"
|
||||
style="@style/QuickSettingsSmallText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="2dp"
|
||||
tools:text="Verified By: E-Corp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/quick_settings_sheet"
|
||||
android:fillViewport="true">
|
||||
<FrameLayout
|
||||
android:id="@+id/connectionDetailsInfoLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</androidx.core.widget.NestedScrollView>
|
|
@ -35,8 +35,10 @@
|
|||
style="@style/QuickSettingsText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="8dp"
|
||||
tools:text="https://wikipedia.org" />
|
||||
tools:text="https://wikipedia.org"
|
||||
tools:ignore="RtlSymmetry" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
|
@ -44,16 +46,13 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:minHeight="@dimen/tracking_protection_item_height"
|
||||
android:orientation="horizontal"
|
||||
style="@style/QuickSettingsText"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
style="@style/QuickSettingsText">
|
||||
<ImageView
|
||||
android:id="@+id/securityInfoIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAccessibility="no"
|
||||
app:srcCompat="@drawable/mozac_ic_lock"
|
||||
android:paddingEnd="8dp"/>
|
||||
app:srcCompat="@drawable/mozac_ic_lock"/>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -61,8 +60,9 @@
|
|||
<TextView
|
||||
android:id="@+id/securityInfo"
|
||||
style="@style/QuickSettingsLargeText"
|
||||
android:minHeight="@dimen/quicksettings_item_height"
|
||||
android:layout_width="match_parent"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingStart="8dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:drawableEndCompat="@drawable/ic_arrowhead_right"
|
||||
android:paddingEnd="0dp"
|
||||
|
|
|
@ -110,6 +110,9 @@
|
|||
<action
|
||||
android:id="@+id/action_global_quickSettingsSheetDialogFragment"
|
||||
app:destination="@id/quickSettingsSheetDialogFragment" />
|
||||
<action
|
||||
android:id="@+id/action_global_connectionDetailsDialogFragment"
|
||||
app:destination="@id/connectionPanelDialogFragment" />
|
||||
<action
|
||||
android:id="@+id/action_global_tabsTrayFragment"
|
||||
app:destination="@id/tabsTrayFragment"
|
||||
|
@ -813,6 +816,35 @@
|
|||
android:defaultValue="80"
|
||||
app:argType="integer" />
|
||||
</dialog>
|
||||
<dialog
|
||||
android:id="@+id/connectionPanelDialogFragment"
|
||||
android:name="org.mozilla.fenix.settings.quicksettings.ConnectionPanelDialogFragment"
|
||||
tools:layout="@layout/quicksettings_website_info">
|
||||
<argument
|
||||
android:name="sessionId"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="title"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="url"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="isSecured"
|
||||
app:argType="boolean" />
|
||||
<argument
|
||||
android:name="certificateName"
|
||||
android:defaultValue=" "
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="sitePermissions"
|
||||
app:argType="mozilla.components.concept.engine.permission.SitePermissions"
|
||||
app:nullable="true" />
|
||||
<argument
|
||||
android:name="gravity"
|
||||
android:defaultValue="80"
|
||||
app:argType="integer" />
|
||||
</dialog>
|
||||
<fragment
|
||||
android:id="@+id/trackingProtectionBlockingFragment"
|
||||
android:name="org.mozilla.fenix.trackingprotection.TrackingProtectionBlockingFragment"
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/* 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 io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class ConnectionDetailsInteractorTest {
|
||||
|
||||
private lateinit var controller: ConnectionDetailsController
|
||||
private lateinit var interactor: ConnectionDetailsInteractor
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
controller = mockk(relaxed = true)
|
||||
interactor = ConnectionDetailsInteractor(controller)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN onBackPressed is called THEN delegate the controller`() {
|
||||
interactor.onBackPressed()
|
||||
|
||||
verify {
|
||||
controller.handleBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/* 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 androidx.fragment.app.Fragment
|
||||
import androidx.navigation.NavController
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import io.mockk.spyk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import mozilla.components.browser.state.state.TabSessionState
|
||||
import mozilla.components.browser.state.state.createTab
|
||||
import mozilla.components.concept.engine.permission.SitePermissions
|
||||
import mozilla.components.feature.session.TrackingProtectionUseCases
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.browser.BrowserFragmentDirections
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.nav
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class DefaultConnectionDetailsControllerTest {
|
||||
|
||||
private lateinit var context: Context
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var navController: NavController
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var fragment: Fragment
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var sitePermissions: SitePermissions
|
||||
|
||||
@MockK(relaxed = true)
|
||||
private lateinit var dismiss: () -> Unit
|
||||
|
||||
private lateinit var controller: DefaultConnectionDetailsController
|
||||
|
||||
private lateinit var tab: TabSessionState
|
||||
|
||||
private var gravity = 54
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockKAnnotations.init(this)
|
||||
val trackingProtectionUseCases: TrackingProtectionUseCases = mockk(relaxed = true)
|
||||
context = spyk(testContext)
|
||||
tab = createTab("https://mozilla.org")
|
||||
controller = DefaultConnectionDetailsController(
|
||||
fragment = fragment,
|
||||
context = context,
|
||||
navController = navController,
|
||||
sitePermissions = sitePermissions,
|
||||
gravity = gravity,
|
||||
getCurrentTab = { tab },
|
||||
dismiss = dismiss
|
||||
)
|
||||
|
||||
every { fragment.context } returns context
|
||||
every { context.components.useCases.trackingProtectionUseCases } returns trackingProtectionUseCases
|
||||
|
||||
val onComplete = slot<(Boolean) -> Unit>()
|
||||
every {
|
||||
trackingProtectionUseCases.containsException.invoke(
|
||||
any(),
|
||||
capture(onComplete)
|
||||
)
|
||||
}.answers { onComplete.captured.invoke(true) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN handleBackPressed is called THEN should call dismiss and navigate`() {
|
||||
controller.handleBackPressed()
|
||||
|
||||
verify {
|
||||
dismiss.invoke()
|
||||
|
||||
navController.nav(
|
||||
R.id.quickSettingsSheetDialogFragment,
|
||||
BrowserFragmentDirections.actionGlobalQuickSettingsSheetDialogFragment(
|
||||
sessionId = tab.id,
|
||||
url = tab.content.url,
|
||||
title = tab.content.title,
|
||||
isSecured = tab.content.securityInfo.secure,
|
||||
sitePermissions = sitePermissions,
|
||||
certificateName = tab.content.securityInfo.issuer,
|
||||
permissionHighlights = tab.content.permissionHighlights,
|
||||
isTrackingProtectionEnabled = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -388,4 +388,38 @@ class DefaultQuickSettingsControllerTest {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN handleConnectionDetailsClicked THEN call dismiss and navigate to the connection details dialog`() {
|
||||
every { context.components.core.store } returns browserStore
|
||||
every { context.components.settings } returns appSettings
|
||||
every { context.components.settings.toolbarPosition.androidGravity } returns mockk(relaxed = true)
|
||||
|
||||
val state = WebsiteInfoState.createWebsiteInfoState(
|
||||
websiteUrl = tab.content.url,
|
||||
websiteTitle = tab.content.title,
|
||||
isSecured = true,
|
||||
certificateName = "certificateName"
|
||||
)
|
||||
|
||||
every { store.state.webInfoState } returns state
|
||||
|
||||
controller.handleConnectionDetailsClicked()
|
||||
|
||||
verify {
|
||||
dismiss.invoke()
|
||||
|
||||
navController.nav(
|
||||
R.id.quickSettingsSheetDialogFragment,
|
||||
QuickSettingsSheetDialogFragmentDirections.actionGlobalConnectionDetailsDialogFragment(
|
||||
sessionId = tab.id,
|
||||
url = state.websiteUrl,
|
||||
title = state.websiteTitle,
|
||||
isSecured = true,
|
||||
sitePermissions = sitePermissions,
|
||||
gravity = context.components.settings.toolbarPosition.androidGravity
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import org.mozilla.fenix.ext.components
|
|||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
import org.mozilla.fenix.settings.PhoneFeature
|
||||
import org.mozilla.fenix.settings.quicksettings.QuickSettingsFragmentStore.Companion.toWebsitePermission
|
||||
import org.mozilla.fenix.settings.quicksettings.WebsiteInfoState.Companion.createWebsiteInfoState
|
||||
import org.mozilla.fenix.settings.quicksettings.ext.shouldBeEnabled
|
||||
import org.mozilla.fenix.settings.quicksettings.ext.shouldBeVisible
|
||||
import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_BLOCK_ALL
|
||||
|
@ -100,7 +101,7 @@ class QuickSettingsFragmentStoreTest {
|
|||
val certificateIssuer = "issuer"
|
||||
val securedStatus = true
|
||||
|
||||
val state = QuickSettingsFragmentStore.createWebsiteInfoState(websiteUrl, websiteTitle, securedStatus, certificateIssuer)
|
||||
val state = createWebsiteInfoState(websiteUrl, websiteTitle, securedStatus, certificateIssuer)
|
||||
|
||||
assertNotNull(state)
|
||||
assertSame(websiteUrl, state.websiteUrl)
|
||||
|
@ -115,7 +116,7 @@ class QuickSettingsFragmentStoreTest {
|
|||
val certificateIssuer = "issuer"
|
||||
val securedStatus = false
|
||||
|
||||
val state = QuickSettingsFragmentStore.createWebsiteInfoState(websiteUrl, websiteTitle, securedStatus, certificateIssuer)
|
||||
val state = createWebsiteInfoState(websiteUrl, websiteTitle, securedStatus, certificateIssuer)
|
||||
|
||||
assertNotNull(state)
|
||||
assertSame(websiteUrl, state.websiteUrl)
|
||||
|
|
|
@ -73,4 +73,13 @@ class QuickSettingsInteractorTest {
|
|||
controller.handleBlockedItemsClicked()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN calling onConnectionDetailsClicked THEN delegate to the controller`() {
|
||||
interactor.onConnectionDetailsClicked()
|
||||
|
||||
verify {
|
||||
controller.handleConnectionDetailsClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.mozilla.fenix.settings.quicksettings
|
|||
import android.widget.FrameLayout
|
||||
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
|
||||
|
@ -15,6 +16,7 @@ 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
|
||||
|
||||
|
@ -24,13 +26,15 @@ class WebsiteInfoViewTest {
|
|||
private lateinit var view: WebsiteInfoView
|
||||
private lateinit var icons: BrowserIcons
|
||||
private lateinit var binding: QuicksettingsWebsiteInfoBinding
|
||||
private lateinit var interactor: WebSiteInfoInteractor
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
icons = mockk(relaxed = true)
|
||||
view = WebsiteInfoView(FrameLayout(testContext), icons)
|
||||
every { icons.loadIntoView(view.favicon_image, any()) } returns mockk()
|
||||
interactor = mockk(relaxed = true)
|
||||
view = WebsiteInfoView(FrameLayout(testContext), icons, interactor)
|
||||
binding = view.binding
|
||||
every { icons.loadIntoView(view.favicon_image, any()) } returns mockk()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -44,7 +48,7 @@ class WebsiteInfoViewTest {
|
|||
certificateName = ""
|
||||
))
|
||||
|
||||
verify { icons.loadIntoView(binding.favicon_image, IconRequest(websiteUrl)) }
|
||||
verify { icons.loadIntoView(binding..favicon_image, IconRequest(websiteUrl)) }
|
||||
|
||||
assertEquals("mozilla.org", binding.url.text)
|
||||
assertEquals("Secure Connection", binding.securityInfo.text)
|
||||
|
@ -63,4 +67,55 @@ class WebsiteInfoViewTest {
|
|||
|
||||
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 = 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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue