parent
8b923fc7a4
commit
1d28f63737
|
@ -67,6 +67,7 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
|
||||||
import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
|
import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
|
||||||
import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder
|
import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder
|
||||||
import org.mozilla.fenix.components.metrics.Event
|
import org.mozilla.fenix.components.metrics.Event
|
||||||
|
import org.mozilla.fenix.exceptions.trackingprotection.TrackingProtectionExceptionsFragmentDirections
|
||||||
import org.mozilla.fenix.ext.alreadyOnDestination
|
import org.mozilla.fenix.ext.alreadyOnDestination
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.metrics
|
import org.mozilla.fenix.ext.metrics
|
||||||
|
@ -98,7 +99,6 @@ import org.mozilla.fenix.sync.SyncedTabsFragmentDirections
|
||||||
import org.mozilla.fenix.tabtray.TabTrayDialogFragment
|
import org.mozilla.fenix.tabtray.TabTrayDialogFragment
|
||||||
import org.mozilla.fenix.theme.DefaultThemeManager
|
import org.mozilla.fenix.theme.DefaultThemeManager
|
||||||
import org.mozilla.fenix.theme.ThemeManager
|
import org.mozilla.fenix.theme.ThemeManager
|
||||||
import org.mozilla.fenix.trackingprotectionexceptions.TrackingProtectionExceptionsFragmentDirections
|
|
||||||
import org.mozilla.fenix.utils.BrowsersCache
|
import org.mozilla.fenix.utils.BrowsersCache
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
/* 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.exceptions
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder
|
||||||
|
import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder
|
||||||
|
import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter for a list of sites that are exempted from saving logins or tracking protection,
|
||||||
|
* along with controls to remove the exception.
|
||||||
|
*/
|
||||||
|
abstract class ExceptionsAdapter<T : Any>(
|
||||||
|
private val interactor: ExceptionsInteractor<T>,
|
||||||
|
diffCallback: DiffUtil.ItemCallback<AdapterItem>
|
||||||
|
) : ListAdapter<ExceptionsAdapter.AdapterItem, RecyclerView.ViewHolder>(diffCallback) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the list of items that are displayed.
|
||||||
|
* Header and footer items are added to the list as well.
|
||||||
|
*/
|
||||||
|
fun updateData(exceptions: List<T>) {
|
||||||
|
val adapterItems: List<AdapterItem> = listOf(AdapterItem.Header) +
|
||||||
|
exceptions.map { wrapAdapterItem(it) } +
|
||||||
|
listOf(AdapterItem.DeleteButton)
|
||||||
|
submitList(adapterItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout to use for the delete button.
|
||||||
|
*/
|
||||||
|
@get:LayoutRes
|
||||||
|
abstract val deleteButtonLayoutId: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String to use for the exceptions list header.
|
||||||
|
*/
|
||||||
|
@get:StringRes
|
||||||
|
abstract val headerDescriptionResource: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an item from [updateData] into an adapter item.
|
||||||
|
*/
|
||||||
|
abstract fun wrapAdapterItem(item: T): AdapterItem.Item<T>
|
||||||
|
|
||||||
|
final override fun getItemViewType(position: Int) = when (getItem(position)) {
|
||||||
|
AdapterItem.DeleteButton -> deleteButtonLayoutId
|
||||||
|
AdapterItem.Header -> ExceptionsHeaderViewHolder.LAYOUT_ID
|
||||||
|
is AdapterItem.Item<*> -> ExceptionsListItemViewHolder.LAYOUT_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
||||||
|
|
||||||
|
return when (viewType) {
|
||||||
|
deleteButtonLayoutId ->
|
||||||
|
ExceptionsDeleteButtonViewHolder(view, interactor)
|
||||||
|
ExceptionsHeaderViewHolder.LAYOUT_ID ->
|
||||||
|
ExceptionsHeaderViewHolder(view, headerDescriptionResource)
|
||||||
|
ExceptionsListItemViewHolder.LAYOUT_ID ->
|
||||||
|
ExceptionsListItemViewHolder(view, interactor)
|
||||||
|
else -> throw IllegalStateException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("Unchecked_Cast")
|
||||||
|
final override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
|
if (holder is ExceptionsListItemViewHolder<*>) {
|
||||||
|
holder as ExceptionsListItemViewHolder<T>
|
||||||
|
val adapterItem = getItem(position) as AdapterItem.Item<T>
|
||||||
|
holder.bind(adapterItem.item, adapterItem.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal items for [ExceptionsAdapter]
|
||||||
|
*/
|
||||||
|
sealed class AdapterItem {
|
||||||
|
object DeleteButton : AdapterItem()
|
||||||
|
object Header : AdapterItem()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an item to display in [ExceptionsAdapter].
|
||||||
|
* [T] should refer to the same value as in the [ExceptionsAdapter] and [ExceptionsInteractor].
|
||||||
|
*/
|
||||||
|
abstract class Item<T> : AdapterItem() {
|
||||||
|
abstract val item: T
|
||||||
|
abstract val url: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.exceptions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for exceptions view interactors. This interface is implemented by objects that want
|
||||||
|
* to respond to user interaction on the [ExceptionsView].
|
||||||
|
*/
|
||||||
|
interface ExceptionsInteractor<T> {
|
||||||
|
/**
|
||||||
|
* Called whenever all exception items are deleted
|
||||||
|
*/
|
||||||
|
fun onDeleteAll()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever one exception item is deleted
|
||||||
|
*/
|
||||||
|
fun onDeleteOne(item: T)
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/* 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.exceptions
|
||||||
|
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import kotlinx.android.extensions.LayoutContainer
|
||||||
|
import kotlinx.android.synthetic.main.component_exceptions.*
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View that contains and configures the Exceptions List
|
||||||
|
*/
|
||||||
|
abstract class ExceptionsView<T : Any>(
|
||||||
|
container: ViewGroup,
|
||||||
|
protected val interactor: ExceptionsInteractor<T>
|
||||||
|
) : LayoutContainer {
|
||||||
|
|
||||||
|
override val containerView: FrameLayout = LayoutInflater.from(container.context)
|
||||||
|
.inflate(R.layout.component_exceptions, container, true)
|
||||||
|
.findViewById(R.id.exceptions_wrapper)
|
||||||
|
|
||||||
|
protected abstract val exceptionsAdapter: ExceptionsAdapter<T>
|
||||||
|
|
||||||
|
init {
|
||||||
|
exceptions_list.apply {
|
||||||
|
layoutManager = LinearLayoutManager(containerView.context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun update(items: List<T>) {
|
||||||
|
exceptions_empty_view.isVisible = items.isEmpty()
|
||||||
|
exceptions_list.isVisible = items.isNotEmpty()
|
||||||
|
exceptionsAdapter.updateData(items)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.loginexceptions
|
package org.mozilla.fenix.exceptions.login
|
||||||
|
|
||||||
import mozilla.components.feature.logins.exceptions.LoginException
|
import mozilla.components.feature.logins.exceptions.LoginException
|
||||||
import mozilla.components.lib.state.Action
|
import mozilla.components.lib.state.Action
|
||||||
|
@ -26,7 +26,7 @@ sealed class ExceptionsFragmentAction : Action {
|
||||||
* The state for the Exceptions Screen
|
* The state for the Exceptions Screen
|
||||||
* @property items List of exceptions to display
|
* @property items List of exceptions to display
|
||||||
*/
|
*/
|
||||||
data class ExceptionsFragmentState(val items: List<LoginException>) : State
|
data class ExceptionsFragmentState(val items: List<LoginException> = emptyList()) : State
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ExceptionsState Reducer.
|
* The ExceptionsState Reducer.
|
|
@ -0,0 +1,44 @@
|
||||||
|
/* 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.exceptions.login
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import mozilla.components.feature.logins.exceptions.LoginException
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.exceptions.ExceptionsAdapter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter for a list of sites that are exempted from saving logins,
|
||||||
|
* along with controls to remove the exception.
|
||||||
|
*/
|
||||||
|
class LoginExceptionsAdapter(
|
||||||
|
interactor: LoginExceptionsInteractor
|
||||||
|
) : ExceptionsAdapter<LoginException>(interactor, DiffCallback) {
|
||||||
|
|
||||||
|
override val deleteButtonLayoutId = R.layout.delete_logins_exceptions_button
|
||||||
|
override val headerDescriptionResource = R.string.preferences_passwords_exceptions_description
|
||||||
|
|
||||||
|
override fun wrapAdapterItem(item: LoginException) =
|
||||||
|
LoginAdapterItem(item)
|
||||||
|
|
||||||
|
data class LoginAdapterItem(
|
||||||
|
override val item: LoginException
|
||||||
|
) : AdapterItem.Item<LoginException>() {
|
||||||
|
override val url get() = item.origin
|
||||||
|
}
|
||||||
|
|
||||||
|
internal object DiffCallback : DiffUtil.ItemCallback<AdapterItem>() {
|
||||||
|
override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
||||||
|
when (oldItem) {
|
||||||
|
AdapterItem.DeleteButton, AdapterItem.Header -> oldItem === newItem
|
||||||
|
is LoginAdapterItem -> newItem is LoginAdapterItem && oldItem.item.id == newItem.item.id
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DiffUtilEquals")
|
||||||
|
override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
||||||
|
oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.loginexceptions
|
package org.mozilla.fenix.exceptions.login
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
@ -13,10 +13,9 @@ import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.observe
|
import androidx.lifecycle.observe
|
||||||
import kotlinx.android.synthetic.main.fragment_exceptions.view.*
|
import kotlinx.android.synthetic.main.fragment_exceptions.view.*
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.plus
|
||||||
import mozilla.components.feature.logins.exceptions.LoginException
|
|
||||||
import mozilla.components.lib.state.ext.consumeFrom
|
import mozilla.components.lib.state.ext.consumeFrom
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.components.StoreProvider
|
import org.mozilla.fenix.components.StoreProvider
|
||||||
|
@ -45,14 +44,17 @@ class LoginExceptionsFragment : Fragment() {
|
||||||
val view = inflater.inflate(R.layout.fragment_exceptions, container, false)
|
val view = inflater.inflate(R.layout.fragment_exceptions, container, false)
|
||||||
exceptionsStore = StoreProvider.get(this) {
|
exceptionsStore = StoreProvider.get(this) {
|
||||||
ExceptionsFragmentStore(
|
ExceptionsFragmentStore(
|
||||||
ExceptionsFragmentState(
|
ExceptionsFragmentState(items = emptyList())
|
||||||
items = listOf()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
exceptionsInteractor =
|
exceptionsInteractor = DefaultLoginExceptionsInteractor(
|
||||||
LoginExceptionsInteractor(::deleteOneItem, ::deleteAllItems)
|
ioScope = viewLifecycleOwner.lifecycleScope + Dispatchers.IO,
|
||||||
exceptionsView = LoginExceptionsView(view.exceptionsLayout, exceptionsInteractor)
|
loginExceptionStorage = requireComponents.core.loginExceptionStorage
|
||||||
|
)
|
||||||
|
exceptionsView = LoginExceptionsView(
|
||||||
|
view.exceptionsLayout,
|
||||||
|
exceptionsInteractor
|
||||||
|
)
|
||||||
subscribeToLoginExceptions()
|
subscribeToLoginExceptions()
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
@ -67,19 +69,7 @@ class LoginExceptionsFragment : Fragment() {
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
consumeFrom(exceptionsStore) {
|
consumeFrom(exceptionsStore) {
|
||||||
exceptionsView.update(it)
|
exceptionsView.update(it.items)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteAllItems() {
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch(IO) {
|
|
||||||
requireComponents.core.loginExceptionStorage.deleteAllLoginExceptions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteOneItem(item: LoginException) {
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch(IO) {
|
|
||||||
requireComponents.core.loginExceptionStorage.removeLoginException(item)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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.exceptions.login
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import mozilla.components.feature.logins.exceptions.LoginException
|
||||||
|
import mozilla.components.feature.logins.exceptions.LoginExceptionStorage
|
||||||
|
import org.mozilla.fenix.exceptions.ExceptionsInteractor
|
||||||
|
|
||||||
|
interface LoginExceptionsInteractor : ExceptionsInteractor<LoginException>
|
||||||
|
|
||||||
|
class DefaultLoginExceptionsInteractor(
|
||||||
|
private val ioScope: CoroutineScope,
|
||||||
|
private val loginExceptionStorage: LoginExceptionStorage
|
||||||
|
) : LoginExceptionsInteractor {
|
||||||
|
|
||||||
|
override fun onDeleteAll() {
|
||||||
|
ioScope.launch {
|
||||||
|
loginExceptionStorage.deleteAllLoginExceptions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeleteOne(item: LoginException) {
|
||||||
|
ioScope.launch {
|
||||||
|
loginExceptionStorage.removeLoginException(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/* 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.exceptions.login
|
||||||
|
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import kotlinx.android.synthetic.main.component_exceptions.*
|
||||||
|
import mozilla.components.feature.logins.exceptions.LoginException
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.exceptions.ExceptionsView
|
||||||
|
|
||||||
|
class LoginExceptionsView(
|
||||||
|
container: ViewGroup,
|
||||||
|
interactor: LoginExceptionsInteractor
|
||||||
|
) : ExceptionsView<LoginException>(container, interactor) {
|
||||||
|
|
||||||
|
override val exceptionsAdapter = LoginExceptionsAdapter(interactor)
|
||||||
|
|
||||||
|
init {
|
||||||
|
exceptions_learn_more.isVisible = false
|
||||||
|
exceptions_empty_message.text =
|
||||||
|
containerView.context.getString(R.string.preferences_passwords_exceptions_description_empty)
|
||||||
|
exceptions_list.apply {
|
||||||
|
adapter = exceptionsAdapter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,19 +2,13 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.trackingprotectionexceptions
|
package org.mozilla.fenix.exceptions.trackingprotection
|
||||||
|
|
||||||
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
||||||
import mozilla.components.lib.state.Action
|
import mozilla.components.lib.state.Action
|
||||||
import mozilla.components.lib.state.State
|
import mozilla.components.lib.state.State
|
||||||
import mozilla.components.lib.state.Store
|
import mozilla.components.lib.state.Store
|
||||||
|
|
||||||
/**
|
|
||||||
* Class representing an exception item
|
|
||||||
* @property url Host of the exception
|
|
||||||
*/
|
|
||||||
data class ExceptionItem(override val url: String) : TrackingProtectionException
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The [Store] for holding the [ExceptionsFragmentState] and applying [ExceptionsFragmentAction]s.
|
* The [Store] for holding the [ExceptionsFragmentState] and applying [ExceptionsFragmentAction]s.
|
||||||
*/
|
*/
|
||||||
|
@ -32,7 +26,7 @@ sealed class ExceptionsFragmentAction : Action {
|
||||||
* The state for the Exceptions Screen
|
* The state for the Exceptions Screen
|
||||||
* @property items List of exceptions to display
|
* @property items List of exceptions to display
|
||||||
*/
|
*/
|
||||||
data class ExceptionsFragmentState(val items: List<TrackingProtectionException>) : State
|
data class ExceptionsFragmentState(val items: List<TrackingProtectionException> = emptyList()) : State
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ExceptionsState Reducer.
|
* The ExceptionsState Reducer.
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* 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.exceptions.trackingprotection
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.exceptions.ExceptionsAdapter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter for a list of sites that are exempted from Tracking Protection,
|
||||||
|
* along with controls to remove the exception.
|
||||||
|
*/
|
||||||
|
class TrackingProtectionExceptionsAdapter(
|
||||||
|
interactor: TrackingProtectionExceptionsInteractor
|
||||||
|
) : ExceptionsAdapter<TrackingProtectionException>(interactor, DiffCallback) {
|
||||||
|
|
||||||
|
override val deleteButtonLayoutId = R.layout.delete_exceptions_button
|
||||||
|
override val headerDescriptionResource = R.string.enhanced_tracking_protection_exceptions
|
||||||
|
|
||||||
|
override fun wrapAdapterItem(item: TrackingProtectionException) =
|
||||||
|
TrackingProtectionAdapterItem(item)
|
||||||
|
|
||||||
|
data class TrackingProtectionAdapterItem(
|
||||||
|
override val item: TrackingProtectionException
|
||||||
|
) : AdapterItem.Item<TrackingProtectionException>() {
|
||||||
|
override val url get() = item.url
|
||||||
|
}
|
||||||
|
|
||||||
|
internal object DiffCallback : DiffUtil.ItemCallback<AdapterItem>() {
|
||||||
|
override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
||||||
|
when (oldItem) {
|
||||||
|
AdapterItem.DeleteButton, AdapterItem.Header -> oldItem === newItem
|
||||||
|
is TrackingProtectionAdapterItem ->
|
||||||
|
newItem is TrackingProtectionAdapterItem && oldItem.item.url == newItem.item.url
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DiffUtilEquals")
|
||||||
|
override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
||||||
|
oldItem == newItem
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/* 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.exceptions.trackingprotection
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import kotlinx.android.synthetic.main.fragment_exceptions.view.*
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import mozilla.components.lib.state.ext.consumeFrom
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.components.StoreProvider
|
||||||
|
import org.mozilla.fenix.ext.requireComponents
|
||||||
|
import org.mozilla.fenix.ext.showToolbar
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a list of sites that are exempted from Tracking Protection,
|
||||||
|
* along with controls to remove the exception.
|
||||||
|
*/
|
||||||
|
class TrackingProtectionExceptionsFragment : Fragment() {
|
||||||
|
|
||||||
|
private lateinit var exceptionsStore: ExceptionsFragmentStore
|
||||||
|
private lateinit var exceptionsView: TrackingProtectionExceptionsView
|
||||||
|
private lateinit var exceptionsInteractor: DefaultTrackingProtectionExceptionsInteractor
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
showToolbar(getString(R.string.preference_exceptions))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
val view = inflater.inflate(R.layout.fragment_exceptions, container, false)
|
||||||
|
exceptionsStore = StoreProvider.get(this) {
|
||||||
|
ExceptionsFragmentStore(
|
||||||
|
ExceptionsFragmentState(items = emptyList())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
exceptionsInteractor = DefaultTrackingProtectionExceptionsInteractor(
|
||||||
|
activity = activity as HomeActivity,
|
||||||
|
exceptionsStore = exceptionsStore,
|
||||||
|
trackingProtectionUseCases = requireComponents.useCases.trackingProtectionUseCases
|
||||||
|
)
|
||||||
|
exceptionsView = TrackingProtectionExceptionsView(
|
||||||
|
view.exceptionsLayout,
|
||||||
|
exceptionsInteractor
|
||||||
|
)
|
||||||
|
exceptionsInteractor.reloadExceptions()
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExperimentalCoroutinesApi
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
consumeFrom(exceptionsStore) {
|
||||||
|
exceptionsView.update(it.items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/* 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.exceptions.trackingprotection
|
||||||
|
|
||||||
|
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
||||||
|
import mozilla.components.feature.session.TrackingProtectionUseCases
|
||||||
|
import org.mozilla.fenix.BrowserDirection
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.exceptions.ExceptionsInteractor
|
||||||
|
import org.mozilla.fenix.settings.SupportUtils
|
||||||
|
|
||||||
|
interface TrackingProtectionExceptionsInteractor : ExceptionsInteractor<TrackingProtectionException> {
|
||||||
|
/**
|
||||||
|
* Called whenever learn more about tracking protection is tapped
|
||||||
|
*/
|
||||||
|
fun onLearnMore()
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultTrackingProtectionExceptionsInteractor(
|
||||||
|
private val activity: HomeActivity,
|
||||||
|
private val exceptionsStore: ExceptionsFragmentStore,
|
||||||
|
private val trackingProtectionUseCases: TrackingProtectionUseCases
|
||||||
|
) : TrackingProtectionExceptionsInteractor {
|
||||||
|
|
||||||
|
override fun onLearnMore() {
|
||||||
|
activity.openToBrowserAndLoad(
|
||||||
|
searchTermOrURL = SupportUtils.getGenericSumoURLForTopic(
|
||||||
|
SupportUtils.SumoTopic.TRACKING_PROTECTION
|
||||||
|
),
|
||||||
|
newTab = true,
|
||||||
|
from = BrowserDirection.FromTrackingProtectionExceptions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeleteAll() {
|
||||||
|
trackingProtectionUseCases.removeAllExceptions()
|
||||||
|
reloadExceptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeleteOne(item: TrackingProtectionException) {
|
||||||
|
trackingProtectionUseCases.removeException(item)
|
||||||
|
reloadExceptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reloadExceptions() {
|
||||||
|
trackingProtectionUseCases.fetchExceptions { resultList ->
|
||||||
|
exceptionsStore.dispatch(
|
||||||
|
ExceptionsFragmentAction.Change(resultList)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/* 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.exceptions.trackingprotection
|
||||||
|
|
||||||
|
import android.text.method.LinkMovementMethod
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import kotlinx.android.synthetic.main.component_exceptions.*
|
||||||
|
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
||||||
|
import org.mozilla.fenix.exceptions.ExceptionsView
|
||||||
|
import org.mozilla.fenix.ext.addUnderline
|
||||||
|
|
||||||
|
class TrackingProtectionExceptionsView(
|
||||||
|
container: ViewGroup,
|
||||||
|
interactor: TrackingProtectionExceptionsInteractor
|
||||||
|
) : ExceptionsView<TrackingProtectionException>(container, interactor) {
|
||||||
|
|
||||||
|
override val exceptionsAdapter = TrackingProtectionExceptionsAdapter(interactor)
|
||||||
|
|
||||||
|
init {
|
||||||
|
exceptions_list.apply {
|
||||||
|
adapter = exceptionsAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
with(exceptions_learn_more) {
|
||||||
|
addUnderline()
|
||||||
|
|
||||||
|
movementMethod = LinkMovementMethod.getInstance()
|
||||||
|
setOnClickListener { interactor.onLearnMore() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,27 +2,23 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.trackingprotectionexceptions.viewholders
|
package org.mozilla.fenix.exceptions.viewholders
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlinx.android.synthetic.main.delete_exceptions_button.view.*
|
import com.google.android.material.button.MaterialButton
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.trackingprotectionexceptions.ExceptionsInteractor
|
import org.mozilla.fenix.exceptions.ExceptionsInteractor
|
||||||
|
|
||||||
class ExceptionsDeleteButtonViewHolder(
|
class ExceptionsDeleteButtonViewHolder(
|
||||||
view: View,
|
view: View,
|
||||||
private val interactor: ExceptionsInteractor
|
private val interactor: ExceptionsInteractor<*>
|
||||||
) : RecyclerView.ViewHolder(view) {
|
) : RecyclerView.ViewHolder(view) {
|
||||||
private val deleteButton = view.removeAllExceptions
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
val deleteButton: MaterialButton = view.findViewById(R.id.removeAllExceptions)
|
||||||
deleteButton.setOnClickListener {
|
deleteButton.setOnClickListener {
|
||||||
interactor.onDeleteAll()
|
interactor.onDeleteAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val LAYOUT_ID = R.layout.delete_exceptions_button
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -2,20 +2,21 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.loginexceptions.viewholders
|
package org.mozilla.fenix.exceptions.viewholders
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlinx.android.synthetic.main.exceptions_description.view.*
|
import kotlinx.android.synthetic.main.exceptions_description.view.*
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
|
|
||||||
class LoginExceptionsHeaderViewHolder(
|
class ExceptionsHeaderViewHolder(
|
||||||
view: View
|
view: View,
|
||||||
|
@StringRes description: Int
|
||||||
) : RecyclerView.ViewHolder(view) {
|
) : RecyclerView.ViewHolder(view) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
view.exceptions_description.text =
|
view.exceptions_description.text = view.context.getString(description)
|
||||||
view.context.getString(R.string.preferences_passwords_exceptions_description)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
|
@ -0,0 +1,42 @@
|
||||||
|
/* 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.exceptions.viewholders
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import kotlinx.android.synthetic.main.exception_item.*
|
||||||
|
import mozilla.components.browser.icons.BrowserIcons
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.exceptions.ExceptionsInteractor
|
||||||
|
import org.mozilla.fenix.ext.components
|
||||||
|
import org.mozilla.fenix.ext.loadIntoView
|
||||||
|
import org.mozilla.fenix.utils.view.ViewHolder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View holder for a single website that is exempted from Tracking Protection or Logins.
|
||||||
|
*/
|
||||||
|
class ExceptionsListItemViewHolder<T : Any>(
|
||||||
|
view: View,
|
||||||
|
private val interactor: ExceptionsInteractor<T>,
|
||||||
|
private val icons: BrowserIcons = view.context.components.core.icons
|
||||||
|
) : ViewHolder(view) {
|
||||||
|
|
||||||
|
private lateinit var item: T
|
||||||
|
|
||||||
|
init {
|
||||||
|
delete_exception.setOnClickListener {
|
||||||
|
interactor.onDeleteOne(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(item: T, url: String) {
|
||||||
|
this.item = item
|
||||||
|
webAddressView.text = url
|
||||||
|
icons.loadIntoView(favicon_image, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LAYOUT_ID = R.layout.exception_item
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,83 +0,0 @@
|
||||||
/* 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.loginexceptions
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import mozilla.components.feature.logins.exceptions.LoginException
|
|
||||||
import org.mozilla.fenix.loginexceptions.viewholders.LoginExceptionsDeleteButtonViewHolder
|
|
||||||
import org.mozilla.fenix.loginexceptions.viewholders.LoginExceptionsHeaderViewHolder
|
|
||||||
import org.mozilla.fenix.loginexceptions.viewholders.LoginExceptionsListItemViewHolder
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adapter for a list of sites that are exempted from saving logins,
|
|
||||||
* along with controls to remove the exception.
|
|
||||||
*/
|
|
||||||
class LoginExceptionsAdapter(
|
|
||||||
private val interactor: LoginExceptionsInteractor
|
|
||||||
) : ListAdapter<LoginExceptionsAdapter.AdapterItem, RecyclerView.ViewHolder>(DiffCallback) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change the list of items that are displayed.
|
|
||||||
* Header and footer items are added to the list as well.
|
|
||||||
*/
|
|
||||||
fun updateData(exceptions: List<LoginException>) {
|
|
||||||
val adapterItems: List<AdapterItem> = listOf(AdapterItem.Header) +
|
|
||||||
exceptions.map { AdapterItem.Item(it) } +
|
|
||||||
listOf(AdapterItem.DeleteButton)
|
|
||||||
submitList(adapterItems)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemViewType(position: Int) = when (getItem(position)) {
|
|
||||||
AdapterItem.DeleteButton -> LoginExceptionsDeleteButtonViewHolder.LAYOUT_ID
|
|
||||||
AdapterItem.Header -> LoginExceptionsHeaderViewHolder.LAYOUT_ID
|
|
||||||
is AdapterItem.Item -> LoginExceptionsListItemViewHolder.LAYOUT_ID
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
|
||||||
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
|
||||||
|
|
||||||
return when (viewType) {
|
|
||||||
LoginExceptionsDeleteButtonViewHolder.LAYOUT_ID -> LoginExceptionsDeleteButtonViewHolder(
|
|
||||||
view,
|
|
||||||
interactor
|
|
||||||
)
|
|
||||||
LoginExceptionsHeaderViewHolder.LAYOUT_ID -> LoginExceptionsHeaderViewHolder(view)
|
|
||||||
LoginExceptionsListItemViewHolder.LAYOUT_ID -> LoginExceptionsListItemViewHolder(
|
|
||||||
view,
|
|
||||||
interactor
|
|
||||||
)
|
|
||||||
else -> throw IllegalStateException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
||||||
if (holder is LoginExceptionsListItemViewHolder) {
|
|
||||||
val adapterItem = getItem(position) as AdapterItem.Item
|
|
||||||
holder.bind(adapterItem.item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class AdapterItem {
|
|
||||||
object DeleteButton : AdapterItem()
|
|
||||||
object Header : AdapterItem()
|
|
||||||
data class Item(val item: LoginException) : AdapterItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal object DiffCallback : DiffUtil.ItemCallback<AdapterItem>() {
|
|
||||||
override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
|
||||||
when (oldItem) {
|
|
||||||
AdapterItem.DeleteButton, AdapterItem.Header -> oldItem === newItem
|
|
||||||
is AdapterItem.Item -> newItem is AdapterItem.Item && oldItem.item.id == newItem.item.id
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("DiffUtilEquals")
|
|
||||||
override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
|
||||||
oldItem == newItem
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
/* 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.loginexceptions
|
|
||||||
|
|
||||||
import mozilla.components.feature.logins.exceptions.LoginException
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interactor for the exceptions screen
|
|
||||||
* Provides implementations for the ExceptionsViewInteractor
|
|
||||||
*/
|
|
||||||
class LoginExceptionsInteractor(
|
|
||||||
private val deleteOne: (LoginException) -> Unit,
|
|
||||||
private val deleteAll: () -> Unit
|
|
||||||
) : ExceptionsViewInteractor {
|
|
||||||
override fun onDeleteAll() {
|
|
||||||
deleteAll.invoke()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDeleteOne(item: LoginException) {
|
|
||||||
deleteOne.invoke(item)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
/* 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.loginexceptions
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.FrameLayout
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import kotlinx.android.extensions.LayoutContainer
|
|
||||||
import kotlinx.android.synthetic.main.component_exceptions.*
|
|
||||||
import mozilla.components.feature.logins.exceptions.LoginException
|
|
||||||
import org.mozilla.fenix.R
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for the ExceptionsViewInteractor. This interface is implemented by objects that want
|
|
||||||
* to respond to user interaction on the ExceptionsView
|
|
||||||
*/
|
|
||||||
interface ExceptionsViewInteractor {
|
|
||||||
/**
|
|
||||||
* Called whenever all exception items are deleted
|
|
||||||
*/
|
|
||||||
fun onDeleteAll()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called whenever one exception item is deleted
|
|
||||||
*/
|
|
||||||
fun onDeleteOne(item: LoginException)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* View that contains and configures the Exceptions List
|
|
||||||
*/
|
|
||||||
class LoginExceptionsView(
|
|
||||||
container: ViewGroup,
|
|
||||||
val interactor: LoginExceptionsInteractor
|
|
||||||
) : LayoutContainer {
|
|
||||||
|
|
||||||
override val containerView: FrameLayout = LayoutInflater.from(container.context)
|
|
||||||
.inflate(R.layout.component_exceptions, container, true)
|
|
||||||
.findViewById(R.id.exceptions_wrapper)
|
|
||||||
|
|
||||||
private val exceptionsAdapter = LoginExceptionsAdapter(interactor)
|
|
||||||
|
|
||||||
init {
|
|
||||||
exceptions_learn_more.isVisible = false
|
|
||||||
exceptions_empty_message.text =
|
|
||||||
containerView.context.getString(R.string.preferences_passwords_exceptions_description_empty)
|
|
||||||
exceptions_list.apply {
|
|
||||||
adapter = exceptionsAdapter
|
|
||||||
layoutManager = LinearLayoutManager(containerView.context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun update(state: ExceptionsFragmentState) {
|
|
||||||
exceptions_empty_view.isVisible = state.items.isEmpty()
|
|
||||||
exceptions_list.isVisible = state.items.isNotEmpty()
|
|
||||||
exceptionsAdapter.updateData(state.items)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
/* 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.loginexceptions.viewholders
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import kotlinx.android.synthetic.main.delete_exceptions_button.view.*
|
|
||||||
import org.mozilla.fenix.R
|
|
||||||
import org.mozilla.fenix.loginexceptions.LoginExceptionsInteractor
|
|
||||||
|
|
||||||
class LoginExceptionsDeleteButtonViewHolder(
|
|
||||||
view: View,
|
|
||||||
private val interactor: LoginExceptionsInteractor
|
|
||||||
) : RecyclerView.ViewHolder(view) {
|
|
||||||
private val deleteButton = view.removeAllExceptions
|
|
||||||
|
|
||||||
init {
|
|
||||||
deleteButton.setOnClickListener {
|
|
||||||
interactor.onDeleteAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val LAYOUT_ID = R.layout.delete_logins_exceptions_button
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
/* 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.loginexceptions.viewholders
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import kotlinx.android.synthetic.main.exception_item.view.*
|
|
||||||
import mozilla.components.feature.logins.exceptions.LoginException
|
|
||||||
import org.mozilla.fenix.R
|
|
||||||
import org.mozilla.fenix.ext.components
|
|
||||||
import org.mozilla.fenix.ext.loadIntoView
|
|
||||||
import org.mozilla.fenix.loginexceptions.LoginExceptionsInteractor
|
|
||||||
|
|
||||||
/**
|
|
||||||
* View holder for a single website that is exempted from Tracking Protection.
|
|
||||||
*/
|
|
||||||
class LoginExceptionsListItemViewHolder(
|
|
||||||
view: View,
|
|
||||||
private val interactor: LoginExceptionsInteractor
|
|
||||||
) : RecyclerView.ViewHolder(view) {
|
|
||||||
|
|
||||||
private val favicon = view.favicon_image
|
|
||||||
private val url = view.webAddressView
|
|
||||||
private val deleteButton = view.delete_exception
|
|
||||||
|
|
||||||
private var item: LoginException? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
deleteButton.setOnClickListener {
|
|
||||||
item?.let {
|
|
||||||
interactor.onDeleteOne(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bind(item: LoginException) {
|
|
||||||
this.item = item
|
|
||||||
url.text = item.origin
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateFavIcon(url: String) {
|
|
||||||
favicon.context.components.core.icons.loadIntoView(favicon, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val LAYOUT_ID = R.layout.exception_item
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
/* 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.trackingprotectionexceptions
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
|
||||||
import androidx.recyclerview.widget.ListAdapter
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
|
||||||
import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsDeleteButtonViewHolder
|
|
||||||
import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsHeaderViewHolder
|
|
||||||
import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsListItemViewHolder
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adapter for a list of sites that are exempted from Tracking Protection,
|
|
||||||
* along with controls to remove the exception.
|
|
||||||
*/
|
|
||||||
class ExceptionsAdapter(
|
|
||||||
private val interactor: ExceptionsInteractor
|
|
||||||
) : ListAdapter<ExceptionsAdapter.AdapterItem, RecyclerView.ViewHolder>(DiffCallback) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change the list of items that are displayed.
|
|
||||||
* Header and footer items are added to the list as well.
|
|
||||||
*/
|
|
||||||
fun updateData(exceptions: List<TrackingProtectionException>) {
|
|
||||||
val adapterItems = mutableListOf<AdapterItem>()
|
|
||||||
adapterItems.add(AdapterItem.Header)
|
|
||||||
exceptions.mapTo(adapterItems) { AdapterItem.Item(it) }
|
|
||||||
adapterItems.add(AdapterItem.DeleteButton)
|
|
||||||
submitList(adapterItems)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemViewType(position: Int) = when (getItem(position)) {
|
|
||||||
AdapterItem.DeleteButton -> ExceptionsDeleteButtonViewHolder.LAYOUT_ID
|
|
||||||
AdapterItem.Header -> ExceptionsHeaderViewHolder.LAYOUT_ID
|
|
||||||
is AdapterItem.Item -> ExceptionsListItemViewHolder.LAYOUT_ID
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
|
||||||
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
|
|
||||||
|
|
||||||
return when (viewType) {
|
|
||||||
ExceptionsDeleteButtonViewHolder.LAYOUT_ID -> ExceptionsDeleteButtonViewHolder(
|
|
||||||
view,
|
|
||||||
interactor
|
|
||||||
)
|
|
||||||
ExceptionsHeaderViewHolder.LAYOUT_ID -> ExceptionsHeaderViewHolder(view)
|
|
||||||
ExceptionsListItemViewHolder.LAYOUT_ID -> ExceptionsListItemViewHolder(view, interactor)
|
|
||||||
else -> throw IllegalStateException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
||||||
if (holder is ExceptionsListItemViewHolder) {
|
|
||||||
val adapterItem = getItem(position) as AdapterItem.Item
|
|
||||||
holder.bind(adapterItem.item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class AdapterItem {
|
|
||||||
object DeleteButton : AdapterItem()
|
|
||||||
object Header : AdapterItem()
|
|
||||||
data class Item(val item: TrackingProtectionException) : AdapterItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
private object DiffCallback : DiffUtil.ItemCallback<AdapterItem>() {
|
|
||||||
override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
|
||||||
areContentsTheSame(oldItem, newItem)
|
|
||||||
|
|
||||||
@Suppress("DiffUtilEquals")
|
|
||||||
override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) =
|
|
||||||
oldItem == newItem
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
/* 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.trackingprotectionexceptions
|
|
||||||
|
|
||||||
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interactor for the exceptions screen
|
|
||||||
* Provides implementations for the ExceptionsViewInteractor
|
|
||||||
*/
|
|
||||||
class ExceptionsInteractor(
|
|
||||||
private val learnMore: () -> Unit,
|
|
||||||
private val deleteOne: (TrackingProtectionException) -> Unit,
|
|
||||||
private val deleteAll: () -> Unit
|
|
||||||
) : ExceptionsViewInteractor {
|
|
||||||
override fun onLearnMore() {
|
|
||||||
learnMore.invoke()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDeleteAll() {
|
|
||||||
deleteAll.invoke()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDeleteOne(item: TrackingProtectionException) {
|
|
||||||
deleteOne.invoke(item)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
/* 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.trackingprotectionexceptions
|
|
||||||
|
|
||||||
import android.text.method.LinkMovementMethod
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.FrameLayout
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import kotlinx.android.extensions.LayoutContainer
|
|
||||||
import kotlinx.android.synthetic.main.component_exceptions.*
|
|
||||||
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
|
||||||
import org.mozilla.fenix.R
|
|
||||||
import org.mozilla.fenix.ext.addUnderline
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for the ExceptionsViewInteractor. This interface is implemented by objects that want
|
|
||||||
* to respond to user interaction on the ExceptionsView
|
|
||||||
*/
|
|
||||||
interface ExceptionsViewInteractor {
|
|
||||||
/**
|
|
||||||
* Called whenever learn more about tracking protection is tapped
|
|
||||||
*/
|
|
||||||
fun onLearnMore()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called whenever all exception items are deleted
|
|
||||||
*/
|
|
||||||
fun onDeleteAll()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called whenever one exception item is deleted
|
|
||||||
*/
|
|
||||||
fun onDeleteOne(item: TrackingProtectionException)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* View that contains and configures the Exceptions List
|
|
||||||
*/
|
|
||||||
class ExceptionsView(
|
|
||||||
container: ViewGroup,
|
|
||||||
interactor: ExceptionsInteractor
|
|
||||||
) : LayoutContainer {
|
|
||||||
|
|
||||||
override val containerView: FrameLayout = LayoutInflater.from(container.context)
|
|
||||||
.inflate(R.layout.component_exceptions, container, true)
|
|
||||||
.findViewById(R.id.exceptions_wrapper)
|
|
||||||
|
|
||||||
private val exceptionsAdapter =
|
|
||||||
ExceptionsAdapter(
|
|
||||||
interactor
|
|
||||||
)
|
|
||||||
|
|
||||||
init {
|
|
||||||
exceptions_list.apply {
|
|
||||||
adapter = exceptionsAdapter
|
|
||||||
layoutManager = LinearLayoutManager(container.context)
|
|
||||||
}
|
|
||||||
|
|
||||||
with(exceptions_learn_more) {
|
|
||||||
addUnderline()
|
|
||||||
|
|
||||||
movementMethod = LinkMovementMethod.getInstance()
|
|
||||||
setOnClickListener { interactor.onLearnMore() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun update(state: ExceptionsFragmentState) {
|
|
||||||
exceptions_empty_view.isVisible = state.items.isEmpty()
|
|
||||||
exceptions_list.isVisible = state.items.isNotEmpty()
|
|
||||||
exceptionsAdapter.updateData(state.items)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
/* 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.trackingprotectionexceptions
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import kotlinx.android.synthetic.main.fragment_exceptions.view.*
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
|
||||||
import mozilla.components.feature.session.TrackingProtectionUseCases
|
|
||||||
import mozilla.components.lib.state.ext.consumeFrom
|
|
||||||
import org.mozilla.fenix.BrowserDirection
|
|
||||||
import org.mozilla.fenix.HomeActivity
|
|
||||||
import org.mozilla.fenix.R
|
|
||||||
import org.mozilla.fenix.components.StoreProvider
|
|
||||||
import org.mozilla.fenix.ext.components
|
|
||||||
import org.mozilla.fenix.ext.showToolbar
|
|
||||||
import org.mozilla.fenix.settings.SupportUtils
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a list of sites that are exempted from Tracking Protection,
|
|
||||||
* along with controls to remove the exception.
|
|
||||||
*/
|
|
||||||
class TrackingProtectionExceptionsFragment : Fragment() {
|
|
||||||
|
|
||||||
private lateinit var exceptionsStore: ExceptionsFragmentStore
|
|
||||||
private lateinit var exceptionsView: ExceptionsView
|
|
||||||
private lateinit var exceptionsInteractor: ExceptionsInteractor
|
|
||||||
private lateinit var trackingProtectionUseCases: TrackingProtectionUseCases
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
showToolbar(getString(R.string.preference_exceptions))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View? {
|
|
||||||
val view = inflater.inflate(R.layout.fragment_exceptions, container, false)
|
|
||||||
trackingProtectionUseCases = view.context.components.useCases.trackingProtectionUseCases
|
|
||||||
exceptionsStore = StoreProvider.get(this) {
|
|
||||||
ExceptionsFragmentStore(
|
|
||||||
ExceptionsFragmentState(
|
|
||||||
items = emptyList()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
exceptionsInteractor =
|
|
||||||
ExceptionsInteractor(
|
|
||||||
::openLearnMore,
|
|
||||||
::deleteOneItem,
|
|
||||||
::deleteAllItems
|
|
||||||
)
|
|
||||||
exceptionsView =
|
|
||||||
ExceptionsView(
|
|
||||||
view.exceptionsLayout,
|
|
||||||
exceptionsInteractor
|
|
||||||
)
|
|
||||||
reloadExceptions()
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
consumeFrom(exceptionsStore) {
|
|
||||||
exceptionsView.update(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteAllItems() {
|
|
||||||
trackingProtectionUseCases.removeAllExceptions()
|
|
||||||
reloadExceptions()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteOneItem(item: TrackingProtectionException) {
|
|
||||||
trackingProtectionUseCases.removeException(item)
|
|
||||||
Log.e("Remove one exception", "$item")
|
|
||||||
reloadExceptions()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openLearnMore() {
|
|
||||||
(activity as HomeActivity).openToBrowserAndLoad(
|
|
||||||
searchTermOrURL = SupportUtils.getGenericSumoURLForTopic
|
|
||||||
(SupportUtils.SumoTopic.TRACKING_PROTECTION),
|
|
||||||
newTab = true,
|
|
||||||
from = BrowserDirection.FromTrackingProtectionExceptions
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reloadExceptions() {
|
|
||||||
trackingProtectionUseCases.fetchExceptions { resultList ->
|
|
||||||
exceptionsStore.dispatch(
|
|
||||||
ExceptionsFragmentAction.Change(
|
|
||||||
resultList
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
/* 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.trackingprotectionexceptions.viewholders
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import org.mozilla.fenix.R
|
|
||||||
|
|
||||||
class ExceptionsHeaderViewHolder(
|
|
||||||
view: View
|
|
||||||
) : RecyclerView.ViewHolder(view) {
|
|
||||||
companion object {
|
|
||||||
const val LAYOUT_ID = R.layout.exceptions_description
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
/* 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.trackingprotectionexceptions.viewholders
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import kotlinx.android.synthetic.main.exception_item.view.*
|
|
||||||
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
|
||||||
import org.mozilla.fenix.R
|
|
||||||
import org.mozilla.fenix.trackingprotectionexceptions.ExceptionsInteractor
|
|
||||||
import org.mozilla.fenix.ext.components
|
|
||||||
import org.mozilla.fenix.ext.loadIntoView
|
|
||||||
|
|
||||||
/**
|
|
||||||
* View holder for a single website that is exempted from Tracking Protection.
|
|
||||||
*/
|
|
||||||
class ExceptionsListItemViewHolder(
|
|
||||||
view: View,
|
|
||||||
private val interactor: ExceptionsInteractor
|
|
||||||
) : RecyclerView.ViewHolder(view) {
|
|
||||||
|
|
||||||
private val favicon = view.favicon_image
|
|
||||||
private val url = view.webAddressView
|
|
||||||
private val deleteButton = view.delete_exception
|
|
||||||
|
|
||||||
private var item: TrackingProtectionException? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
deleteButton.setOnClickListener {
|
|
||||||
item?.let {
|
|
||||||
interactor.onDeleteOne(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bind(item: TrackingProtectionException) {
|
|
||||||
this.item = item
|
|
||||||
url.text = item.url
|
|
||||||
updateFavIcon(item.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateFavIcon(url: String) {
|
|
||||||
favicon.context.components.core.icons.loadIntoView(favicon, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val LAYOUT_ID = R.layout.exception_item
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -45,5 +45,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:listitem="@layout/exception_item" />
|
tools:listheader="@layout/exceptions_description"
|
||||||
|
tools:listitem="@layout/exception_item"
|
||||||
|
tools:listfooter="@layout/delete_exceptions_button" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
<!-- 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
|
- 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/. -->
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
<com.google.android.material.button.MaterialButton xmlns:android="http://schemas.android.com/apk/res/android"
|
<com.google.android.material.button.MaterialButton
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/removeAllExceptions"
|
android:id="@+id/removeAllExceptions"
|
||||||
style="@style/DestructiveButton"
|
style="@style/DestructiveButton"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
|
|
|
@ -3,10 +3,11 @@
|
||||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
- 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/. -->
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/exceptions_description"
|
android:id="@+id/exceptions_description"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="12dp"
|
android:layout_margin="12dp"
|
||||||
android:text="@string/enhanced_tracking_protection_exceptions"
|
tools:text="@string/enhanced_tracking_protection_exceptions"
|
||||||
android:textColor="?primaryText"
|
android:textColor="?primaryText"
|
||||||
android:textSize="16sp" />
|
android:textSize="16sp" />
|
||||||
|
|
|
@ -347,7 +347,7 @@
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/loginExceptionsFragment"
|
android:id="@+id/loginExceptionsFragment"
|
||||||
android:name="org.mozilla.fenix.loginexceptions.LoginExceptionsFragment"
|
android:name="org.mozilla.fenix.exceptions.login.LoginExceptionsFragment"
|
||||||
android:label="@string/preferences_passwords_exceptions"
|
android:label="@string/preferences_passwords_exceptions"
|
||||||
tools:layout="@layout/fragment_exceptions" />
|
tools:layout="@layout/fragment_exceptions" />
|
||||||
|
|
||||||
|
@ -609,7 +609,7 @@
|
||||||
android:label="@string/preferences_delete_browsing_data" />
|
android:label="@string/preferences_delete_browsing_data" />
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/trackingProtectionExceptionsFragment"
|
android:id="@+id/trackingProtectionExceptionsFragment"
|
||||||
android:name="org.mozilla.fenix.trackingprotectionexceptions.TrackingProtectionExceptionsFragment"
|
android:name="org.mozilla.fenix.exceptions.trackingprotection.TrackingProtectionExceptionsFragment"
|
||||||
android:label="@string/preference_exceptions" />
|
android:label="@string/preference_exceptions" />
|
||||||
<dialog
|
<dialog
|
||||||
android:id="@+id/collectionCreationFragment"
|
android:id="@+id/collectionCreationFragment"
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
/* 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.exceptions.login
|
||||||
|
|
||||||
|
import io.mockk.mockk
|
||||||
|
import mozilla.components.feature.logins.exceptions.LoginException
|
||||||
|
import mozilla.components.support.test.ext.joinBlocking
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNotSame
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class LoginExceptionFragmentStoreTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onChange() {
|
||||||
|
val initialState = ExceptionsFragmentState()
|
||||||
|
val store = ExceptionsFragmentStore(initialState)
|
||||||
|
val newExceptionsItem: LoginException = mockk()
|
||||||
|
|
||||||
|
store.dispatch(ExceptionsFragmentAction.Change(listOf(newExceptionsItem))).joinBlocking()
|
||||||
|
assertNotSame(initialState, store.state)
|
||||||
|
assertEquals(listOf(newExceptionsItem), store.state.items)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,149 @@
|
||||||
|
/* 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.exceptions.login
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.appcompat.view.ContextThemeWrapper
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
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.R
|
||||||
|
import org.mozilla.fenix.exceptions.ExceptionsAdapter
|
||||||
|
import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder
|
||||||
|
import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder
|
||||||
|
import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder
|
||||||
|
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||||
|
|
||||||
|
@RunWith(FenixRobolectricTestRunner::class)
|
||||||
|
class LoginExceptionsAdapterTest {
|
||||||
|
|
||||||
|
private lateinit var interactor: LoginExceptionsInteractor
|
||||||
|
private lateinit var adapter: LoginExceptionsAdapter
|
||||||
|
private lateinit var context: Context
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
interactor = mockk()
|
||||||
|
adapter = LoginExceptionsAdapter(interactor)
|
||||||
|
context = ContextThemeWrapper(testContext, R.style.NormalTheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `creates correct view holder type`() {
|
||||||
|
val parent = FrameLayout(context)
|
||||||
|
adapter.updateData(listOf(mockk(), mockk()))
|
||||||
|
assertEquals(4, adapter.itemCount)
|
||||||
|
|
||||||
|
val holders = (0 until adapter.itemCount).asSequence()
|
||||||
|
.map { i -> adapter.getItemViewType(i) }
|
||||||
|
.map { viewType -> adapter.onCreateViewHolder(parent, viewType) }
|
||||||
|
.toList()
|
||||||
|
assertEquals(4, holders.size)
|
||||||
|
|
||||||
|
assertTrue(holders[0] is ExceptionsHeaderViewHolder)
|
||||||
|
assertTrue(holders[1] is ExceptionsListItemViewHolder<*>)
|
||||||
|
assertTrue(holders[2] is ExceptionsListItemViewHolder<*>)
|
||||||
|
assertTrue(holders[3] is ExceptionsDeleteButtonViewHolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `headers and delete should check if the other object is the same`() {
|
||||||
|
assertTrue(
|
||||||
|
LoginExceptionsAdapter.DiffCallback.areItemsTheSame(
|
||||||
|
ExceptionsAdapter.AdapterItem.Header,
|
||||||
|
ExceptionsAdapter.AdapterItem.Header
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertTrue(
|
||||||
|
LoginExceptionsAdapter.DiffCallback.areItemsTheSame(
|
||||||
|
ExceptionsAdapter.AdapterItem.DeleteButton,
|
||||||
|
ExceptionsAdapter.AdapterItem.DeleteButton
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertFalse(
|
||||||
|
LoginExceptionsAdapter.DiffCallback.areItemsTheSame(
|
||||||
|
ExceptionsAdapter.AdapterItem.Header,
|
||||||
|
ExceptionsAdapter.AdapterItem.DeleteButton
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertTrue(
|
||||||
|
LoginExceptionsAdapter.DiffCallback.areContentsTheSame(
|
||||||
|
ExceptionsAdapter.AdapterItem.Header,
|
||||||
|
ExceptionsAdapter.AdapterItem.Header
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertTrue(
|
||||||
|
LoginExceptionsAdapter.DiffCallback.areContentsTheSame(
|
||||||
|
ExceptionsAdapter.AdapterItem.DeleteButton,
|
||||||
|
ExceptionsAdapter.AdapterItem.DeleteButton
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertFalse(
|
||||||
|
LoginExceptionsAdapter.DiffCallback.areContentsTheSame(
|
||||||
|
ExceptionsAdapter.AdapterItem.DeleteButton,
|
||||||
|
ExceptionsAdapter.AdapterItem.Header
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `items with the same id should be marked as same`() {
|
||||||
|
assertTrue(
|
||||||
|
LoginExceptionsAdapter.DiffCallback.areItemsTheSame(
|
||||||
|
LoginExceptionsAdapter.LoginAdapterItem(
|
||||||
|
mockk {
|
||||||
|
every { id } returns 12L
|
||||||
|
}
|
||||||
|
),
|
||||||
|
LoginExceptionsAdapter.LoginAdapterItem(
|
||||||
|
mockk {
|
||||||
|
every { id } returns 12L
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertFalse(
|
||||||
|
LoginExceptionsAdapter.DiffCallback.areItemsTheSame(
|
||||||
|
LoginExceptionsAdapter.LoginAdapterItem(
|
||||||
|
mockk {
|
||||||
|
every { id } returns 14L
|
||||||
|
}
|
||||||
|
),
|
||||||
|
LoginExceptionsAdapter.LoginAdapterItem(
|
||||||
|
mockk {
|
||||||
|
every { id } returns 12L
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertFalse(
|
||||||
|
LoginExceptionsAdapter.DiffCallback.areItemsTheSame(
|
||||||
|
LoginExceptionsAdapter.LoginAdapterItem(
|
||||||
|
mockk {
|
||||||
|
every { id } returns 14L
|
||||||
|
}
|
||||||
|
),
|
||||||
|
ExceptionsAdapter.AdapterItem.Header
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertFalse(
|
||||||
|
LoginExceptionsAdapter.DiffCallback.areItemsTheSame(
|
||||||
|
ExceptionsAdapter.AdapterItem.DeleteButton,
|
||||||
|
LoginExceptionsAdapter.LoginAdapterItem(
|
||||||
|
mockk {
|
||||||
|
every { id } returns 14L
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/* 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.exceptions.login
|
||||||
|
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.TestCoroutineScope
|
||||||
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
|
import mozilla.components.feature.logins.exceptions.LoginException
|
||||||
|
import mozilla.components.feature.logins.exceptions.LoginExceptionStorage
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@ExperimentalCoroutinesApi
|
||||||
|
class LoginExceptionsInteractorTest {
|
||||||
|
|
||||||
|
private lateinit var loginExceptionStorage: LoginExceptionStorage
|
||||||
|
private lateinit var interactor: LoginExceptionsInteractor
|
||||||
|
private val scope = TestCoroutineScope()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
loginExceptionStorage = mockk(relaxed = true)
|
||||||
|
interactor = DefaultLoginExceptionsInteractor(scope, loginExceptionStorage)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onDeleteAll() = scope.runBlockingTest {
|
||||||
|
interactor.onDeleteAll()
|
||||||
|
verify { loginExceptionStorage.deleteAllLoginExceptions() }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onDeleteOne() = scope.runBlockingTest {
|
||||||
|
val exceptionsItem: LoginException = mockk()
|
||||||
|
interactor.onDeleteOne(exceptionsItem)
|
||||||
|
verify { loginExceptionStorage.removeLoginException(exceptionsItem) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.loginexceptions
|
package org.mozilla.fenix.exceptions.login
|
||||||
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
@ -30,7 +30,10 @@ class LoginExceptionsViewTest {
|
||||||
fun setup() {
|
fun setup() {
|
||||||
parent = FrameLayout(testContext)
|
parent = FrameLayout(testContext)
|
||||||
interactor = mockk()
|
interactor = mockk()
|
||||||
view = LoginExceptionsView(parent, interactor)
|
view = LoginExceptionsView(
|
||||||
|
parent,
|
||||||
|
interactor
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -45,9 +48,7 @@ class LoginExceptionsViewTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `hide list when there are no items`() {
|
fun `hide list when there are no items`() {
|
||||||
view.update(ExceptionsFragmentState(
|
view.update(emptyList())
|
||||||
items = emptyList()
|
|
||||||
))
|
|
||||||
|
|
||||||
assertTrue(view.exceptions_empty_view.isVisible)
|
assertTrue(view.exceptions_empty_view.isVisible)
|
||||||
assertFalse(view.exceptions_list.isVisible)
|
assertFalse(view.exceptions_list.isVisible)
|
||||||
|
@ -55,9 +56,7 @@ class LoginExceptionsViewTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `shows list when there are items`() {
|
fun `shows list when there are items`() {
|
||||||
view.update(ExceptionsFragmentState(
|
view.update(listOf(mockk()))
|
||||||
items = listOf(mockk())
|
|
||||||
))
|
|
||||||
|
|
||||||
assertFalse(view.exceptions_empty_view.isVisible)
|
assertFalse(view.exceptions_empty_view.isVisible)
|
||||||
assertTrue(view.exceptions_list.isVisible)
|
assertTrue(view.exceptions_list.isVisible)
|
|
@ -0,0 +1,151 @@
|
||||||
|
/* 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.exceptions.trackingprotection
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.appcompat.view.ContextThemeWrapper
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
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.R
|
||||||
|
import org.mozilla.fenix.exceptions.ExceptionsAdapter
|
||||||
|
import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder
|
||||||
|
import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder
|
||||||
|
import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder
|
||||||
|
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||||
|
|
||||||
|
@ExperimentalCoroutinesApi
|
||||||
|
@RunWith(FenixRobolectricTestRunner::class)
|
||||||
|
class TrackingProtectionExceptionsAdapterTest {
|
||||||
|
|
||||||
|
private lateinit var interactor: TrackingProtectionExceptionsInteractor
|
||||||
|
private lateinit var adapter: TrackingProtectionExceptionsAdapter
|
||||||
|
private lateinit var context: Context
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
interactor = mockk()
|
||||||
|
adapter = TrackingProtectionExceptionsAdapter(interactor)
|
||||||
|
context = ContextThemeWrapper(testContext, R.style.NormalTheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `creates correct view holder type`() {
|
||||||
|
val parent = FrameLayout(context)
|
||||||
|
adapter.updateData(listOf(mockk(), mockk()))
|
||||||
|
assertEquals(4, adapter.itemCount)
|
||||||
|
|
||||||
|
val holders = (0 until adapter.itemCount).asSequence()
|
||||||
|
.map { i -> adapter.getItemViewType(i) }
|
||||||
|
.map { viewType -> adapter.onCreateViewHolder(parent, viewType) }
|
||||||
|
.toList()
|
||||||
|
assertEquals(4, holders.size)
|
||||||
|
|
||||||
|
assertTrue(holders[0] is ExceptionsHeaderViewHolder)
|
||||||
|
assertTrue(holders[1] is ExceptionsListItemViewHolder<*>)
|
||||||
|
assertTrue(holders[2] is ExceptionsListItemViewHolder<*>)
|
||||||
|
assertTrue(holders[3] is ExceptionsDeleteButtonViewHolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `headers and delete should check if the other object is the same`() {
|
||||||
|
assertTrue(
|
||||||
|
TrackingProtectionExceptionsAdapter.DiffCallback.areItemsTheSame(
|
||||||
|
ExceptionsAdapter.AdapterItem.Header,
|
||||||
|
ExceptionsAdapter.AdapterItem.Header
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertTrue(
|
||||||
|
TrackingProtectionExceptionsAdapter.DiffCallback.areItemsTheSame(
|
||||||
|
ExceptionsAdapter.AdapterItem.DeleteButton,
|
||||||
|
ExceptionsAdapter.AdapterItem.DeleteButton
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertFalse(
|
||||||
|
TrackingProtectionExceptionsAdapter.DiffCallback.areItemsTheSame(
|
||||||
|
ExceptionsAdapter.AdapterItem.Header,
|
||||||
|
ExceptionsAdapter.AdapterItem.DeleteButton
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertTrue(
|
||||||
|
TrackingProtectionExceptionsAdapter.DiffCallback.areContentsTheSame(
|
||||||
|
ExceptionsAdapter.AdapterItem.Header,
|
||||||
|
ExceptionsAdapter.AdapterItem.Header
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertTrue(
|
||||||
|
TrackingProtectionExceptionsAdapter.DiffCallback.areContentsTheSame(
|
||||||
|
ExceptionsAdapter.AdapterItem.DeleteButton,
|
||||||
|
ExceptionsAdapter.AdapterItem.DeleteButton
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertFalse(
|
||||||
|
TrackingProtectionExceptionsAdapter.DiffCallback.areContentsTheSame(
|
||||||
|
ExceptionsAdapter.AdapterItem.DeleteButton,
|
||||||
|
ExceptionsAdapter.AdapterItem.Header
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `items with the same url should be marked as same`() {
|
||||||
|
assertTrue(
|
||||||
|
TrackingProtectionExceptionsAdapter.DiffCallback.areItemsTheSame(
|
||||||
|
TrackingProtectionExceptionsAdapter.TrackingProtectionAdapterItem(
|
||||||
|
mockk {
|
||||||
|
every { url } returns "https://mozilla.org"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TrackingProtectionExceptionsAdapter.TrackingProtectionAdapterItem(
|
||||||
|
mockk {
|
||||||
|
every { url } returns "https://mozilla.org"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertFalse(
|
||||||
|
TrackingProtectionExceptionsAdapter.DiffCallback.areItemsTheSame(
|
||||||
|
TrackingProtectionExceptionsAdapter.TrackingProtectionAdapterItem(
|
||||||
|
mockk {
|
||||||
|
every { url } returns "https://mozilla.org"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TrackingProtectionExceptionsAdapter.TrackingProtectionAdapterItem(
|
||||||
|
mockk {
|
||||||
|
every { url } returns "https://firefox.com"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertFalse(
|
||||||
|
TrackingProtectionExceptionsAdapter.DiffCallback.areItemsTheSame(
|
||||||
|
TrackingProtectionExceptionsAdapter.TrackingProtectionAdapterItem(
|
||||||
|
mockk {
|
||||||
|
every { url } returns "https://mozilla.org"
|
||||||
|
}
|
||||||
|
),
|
||||||
|
ExceptionsAdapter.AdapterItem.Header
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertFalse(
|
||||||
|
TrackingProtectionExceptionsAdapter.DiffCallback.areItemsTheSame(
|
||||||
|
ExceptionsAdapter.AdapterItem.DeleteButton,
|
||||||
|
TrackingProtectionExceptionsAdapter.TrackingProtectionAdapterItem(
|
||||||
|
mockk {
|
||||||
|
every { url } returns "https://mozilla.org"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,21 +2,20 @@
|
||||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
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/. */
|
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.loginexceptions
|
package org.mozilla.fenix.exceptions.trackingprotection
|
||||||
|
|
||||||
import io.mockk.mockk
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import mozilla.components.feature.logins.exceptions.LoginException
|
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNotSame
|
import org.junit.Assert.assertNotSame
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class LoginExceptionFragmentStoreTest {
|
class TrackingProtectionExceptionsFragmentStoreTest {
|
||||||
@Test
|
@Test
|
||||||
fun onChange() = runBlocking {
|
fun onChange() = runBlocking {
|
||||||
val initialState = emptyDefaultState()
|
val initialState = ExceptionsFragmentState()
|
||||||
val store = ExceptionsFragmentStore(initialState)
|
val store = ExceptionsFragmentStore(initialState)
|
||||||
val newExceptionsItem: LoginException = mockk()
|
val newExceptionsItem = ExceptionItem("URL")
|
||||||
|
|
||||||
store.dispatch(ExceptionsFragmentAction.Change(listOf(newExceptionsItem))).join()
|
store.dispatch(ExceptionsFragmentAction.Change(listOf(newExceptionsItem))).join()
|
||||||
assertNotSame(initialState, store.state)
|
assertNotSame(initialState, store.state)
|
||||||
|
@ -26,7 +25,5 @@ class LoginExceptionFragmentStoreTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun emptyDefaultState(): ExceptionsFragmentState = ExceptionsFragmentState(
|
private data class ExceptionItem(override val url: String) : TrackingProtectionException
|
||||||
items = listOf()
|
|
||||||
)
|
|
||||||
}
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
/* 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.exceptions.trackingprotection
|
||||||
|
|
||||||
|
import io.mockk.CapturingSlot
|
||||||
|
import io.mockk.MockKAnnotations
|
||||||
|
import io.mockk.Runs
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.impl.annotations.MockK
|
||||||
|
import io.mockk.just
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.slot
|
||||||
|
import io.mockk.verify
|
||||||
|
import io.mockk.verifySequence
|
||||||
|
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
||||||
|
import mozilla.components.feature.session.TrackingProtectionUseCases
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.BrowserDirection
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.settings.SupportUtils
|
||||||
|
|
||||||
|
class TrackingProtectionExceptionsInteractorTest {
|
||||||
|
|
||||||
|
@MockK(relaxed = true) private lateinit var activity: HomeActivity
|
||||||
|
@MockK(relaxed = true) private lateinit var exceptionsStore: ExceptionsFragmentStore
|
||||||
|
@MockK(relaxed = true) private lateinit var trackingProtectionUseCases: TrackingProtectionUseCases
|
||||||
|
private lateinit var interactor: TrackingProtectionExceptionsInteractor
|
||||||
|
private lateinit var onResult: CapturingSlot<(List<TrackingProtectionException>) -> Unit>
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
MockKAnnotations.init(this)
|
||||||
|
interactor = DefaultTrackingProtectionExceptionsInteractor(
|
||||||
|
activity = activity,
|
||||||
|
exceptionsStore = exceptionsStore,
|
||||||
|
trackingProtectionUseCases = trackingProtectionUseCases
|
||||||
|
)
|
||||||
|
|
||||||
|
onResult = slot()
|
||||||
|
every { trackingProtectionUseCases.fetchExceptions(capture(onResult)) } just Runs
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onLearnMore() {
|
||||||
|
interactor.onLearnMore()
|
||||||
|
|
||||||
|
val supportUrl = SupportUtils.getGenericSumoURLForTopic(
|
||||||
|
SupportUtils.SumoTopic.TRACKING_PROTECTION
|
||||||
|
)
|
||||||
|
verify {
|
||||||
|
activity.openToBrowserAndLoad(
|
||||||
|
searchTermOrURL = supportUrl,
|
||||||
|
newTab = true,
|
||||||
|
from = BrowserDirection.FromTrackingProtectionExceptions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onDeleteAll() {
|
||||||
|
interactor.onDeleteAll()
|
||||||
|
verifySequence {
|
||||||
|
trackingProtectionUseCases.removeAllExceptions()
|
||||||
|
trackingProtectionUseCases.fetchExceptions(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
val results = mockk<List<TrackingProtectionException>>()
|
||||||
|
onResult.captured(results)
|
||||||
|
verify { exceptionsStore.dispatch(ExceptionsFragmentAction.Change(results)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onDeleteOne() {
|
||||||
|
val exceptionsItem = mockk<TrackingProtectionException>()
|
||||||
|
interactor.onDeleteOne(exceptionsItem)
|
||||||
|
verifySequence {
|
||||||
|
trackingProtectionUseCases.removeException(exceptionsItem)
|
||||||
|
trackingProtectionUseCases.fetchExceptions(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
val results = mockk<List<TrackingProtectionException>>()
|
||||||
|
onResult.captured(results)
|
||||||
|
verify { exceptionsStore.dispatch(ExceptionsFragmentAction.Change(results)) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.trackingprotectionexceptions
|
package org.mozilla.fenix.exceptions.trackingprotection
|
||||||
|
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.method.LinkMovementMethod
|
import android.text.method.LinkMovementMethod
|
||||||
|
@ -29,25 +29,28 @@ import org.junit.runner.RunWith
|
||||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||||
|
|
||||||
@RunWith(FenixRobolectricTestRunner::class)
|
@RunWith(FenixRobolectricTestRunner::class)
|
||||||
class ExceptionsViewTest {
|
class TrackingProtectionExceptionsViewTest {
|
||||||
|
|
||||||
private lateinit var container: ViewGroup
|
private lateinit var container: ViewGroup
|
||||||
private lateinit var interactor: ExceptionsInteractor
|
private lateinit var interactor: TrackingProtectionExceptionsInteractor
|
||||||
private lateinit var exceptionsView: ExceptionsView
|
private lateinit var exceptionsView: TrackingProtectionExceptionsView
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
mockkConstructor(ExceptionsAdapter::class)
|
mockkConstructor(TrackingProtectionExceptionsAdapter::class)
|
||||||
container = FrameLayout(testContext)
|
container = FrameLayout(testContext)
|
||||||
interactor = mockk()
|
interactor = mockk()
|
||||||
|
|
||||||
exceptionsView = ExceptionsView(container, interactor)
|
exceptionsView = TrackingProtectionExceptionsView(
|
||||||
every { anyConstructed<ExceptionsAdapter>().updateData(any()) } just Runs
|
container,
|
||||||
|
interactor
|
||||||
|
)
|
||||||
|
every { anyConstructed<TrackingProtectionExceptionsAdapter>().updateData(any()) } just Runs
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun teardown() {
|
fun teardown() {
|
||||||
unmockkConstructor(ExceptionsAdapter::class)
|
unmockkConstructor(TrackingProtectionExceptionsAdapter::class)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -63,20 +66,20 @@ class ExceptionsViewTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `binds empty list to adapter`() {
|
fun `binds empty list to adapter`() {
|
||||||
exceptionsView.update(ExceptionsFragmentState(emptyList()))
|
exceptionsView.update(emptyList())
|
||||||
|
|
||||||
assertTrue(exceptionsView.exceptions_empty_view.isVisible)
|
assertTrue(exceptionsView.exceptions_empty_view.isVisible)
|
||||||
assertFalse(exceptionsView.exceptions_list.isVisible)
|
assertFalse(exceptionsView.exceptions_list.isVisible)
|
||||||
verify { anyConstructed<ExceptionsAdapter>().updateData(emptyList()) }
|
verify { anyConstructed<TrackingProtectionExceptionsAdapter>().updateData(emptyList()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `binds list with items to adapter`() {
|
fun `binds list with items to adapter`() {
|
||||||
val items = listOf<TrackingProtectionException>(mockk(), mockk())
|
val items = listOf<TrackingProtectionException>(mockk(), mockk())
|
||||||
exceptionsView.update(ExceptionsFragmentState(items))
|
exceptionsView.update(items)
|
||||||
|
|
||||||
assertFalse(exceptionsView.exceptions_empty_view.isVisible)
|
assertFalse(exceptionsView.exceptions_empty_view.isVisible)
|
||||||
assertTrue(exceptionsView.exceptions_list.isVisible)
|
assertTrue(exceptionsView.exceptions_list.isVisible)
|
||||||
verify { anyConstructed<ExceptionsAdapter>().updateData(items) }
|
verify { anyConstructed<TrackingProtectionExceptionsAdapter>().updateData(items) }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,12 +2,14 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.loginexceptions.viewholders
|
package org.mozilla.fenix.exceptions.viewholders
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import io.mockk.MockKAnnotations
|
||||||
import io.mockk.Runs
|
import io.mockk.Runs
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
|
import io.mockk.impl.annotations.MockK
|
||||||
import io.mockk.just
|
import io.mockk.just
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.slot
|
import io.mockk.slot
|
||||||
|
@ -15,28 +17,25 @@ import io.mockk.verify
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.loginexceptions.LoginExceptionsInteractor
|
import org.mozilla.fenix.exceptions.ExceptionsInteractor
|
||||||
|
|
||||||
class LoginExceptionsDeleteButtonViewHolderTest {
|
class ExceptionsDeleteButtonViewHolderTest {
|
||||||
|
|
||||||
private lateinit var view: View
|
@MockK private lateinit var view: View
|
||||||
private lateinit var deleteButton: MaterialButton
|
@MockK private lateinit var deleteButton: MaterialButton
|
||||||
private lateinit var interactor: LoginExceptionsInteractor
|
@MockK private lateinit var interactor: ExceptionsInteractor<Unit>
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
deleteButton = mockk()
|
MockKAnnotations.init(this)
|
||||||
view = mockk {
|
every { view.findViewById<MaterialButton>(R.id.removeAllExceptions) } returns deleteButton
|
||||||
every { findViewById<MaterialButton>(R.id.removeAllExceptions) } returns deleteButton
|
|
||||||
}
|
|
||||||
interactor = mockk()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `delete button calls interactor`() {
|
fun `delete button calls interactor`() {
|
||||||
val slot = slot<View.OnClickListener>()
|
val slot = slot<View.OnClickListener>()
|
||||||
every { deleteButton.setOnClickListener(capture(slot)) } just Runs
|
every { deleteButton.setOnClickListener(capture(slot)) } just Runs
|
||||||
LoginExceptionsDeleteButtonViewHolder(view, interactor)
|
ExceptionsDeleteButtonViewHolder(view, interactor)
|
||||||
|
|
||||||
every { interactor.onDeleteAll() } just Runs
|
every { interactor.onDeleteAll() } just Runs
|
||||||
slot.captured.onClick(mockk())
|
slot.captured.onClick(mockk())
|
|
@ -2,7 +2,7 @@
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
package org.mozilla.fenix.loginexceptions.viewholders
|
package org.mozilla.fenix.exceptions.viewholders
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
@ -13,7 +13,7 @@ import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
|
|
||||||
class LoginExceptionsHeaderViewHolderTest {
|
class ExceptionsHeaderViewHolderTest {
|
||||||
|
|
||||||
private lateinit var view: View
|
private lateinit var view: View
|
||||||
private lateinit var description: TextView
|
private lateinit var description: TextView
|
||||||
|
@ -31,7 +31,7 @@ class LoginExceptionsHeaderViewHolderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `sets description text`() {
|
fun `sets description text`() {
|
||||||
LoginExceptionsHeaderViewHolder(view)
|
ExceptionsHeaderViewHolder(view, R.string.preferences_passwords_exceptions_description)
|
||||||
verify { description.text = "Logins and passwords will not be saved for these sites." }
|
verify { description.text = "Logins and passwords will not be saved for these sites." }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/* 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.exceptions.viewholders
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageButton
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import io.mockk.MockKAnnotations
|
||||||
|
import io.mockk.Runs
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.impl.annotations.MockK
|
||||||
|
import io.mockk.just
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.slot
|
||||||
|
import io.mockk.verify
|
||||||
|
import mozilla.components.browser.icons.BrowserIcons
|
||||||
|
import mozilla.components.browser.icons.IconRequest
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mozilla.fenix.R
|
||||||
|
import org.mozilla.fenix.exceptions.ExceptionsInteractor
|
||||||
|
|
||||||
|
class ExceptionsListItemViewHolderTest {
|
||||||
|
|
||||||
|
@MockK private lateinit var view: View
|
||||||
|
@MockK(relaxUnitFun = true) private lateinit var url: TextView
|
||||||
|
@MockK(relaxUnitFun = true) private lateinit var deleteButton: ImageButton
|
||||||
|
@MockK private lateinit var favicon: ImageView
|
||||||
|
@MockK private lateinit var icons: BrowserIcons
|
||||||
|
@MockK private lateinit var interactor: ExceptionsInteractor<Exception>
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
MockKAnnotations.init(this)
|
||||||
|
|
||||||
|
every { view.findViewById<TextView>(R.id.webAddressView) } returns url
|
||||||
|
every { view.findViewById<ImageButton>(R.id.delete_exception) } returns deleteButton
|
||||||
|
every { view.findViewById<ImageView>(R.id.favicon_image) } returns favicon
|
||||||
|
every { icons.loadIntoView(favicon, any()) } returns mockk()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `sets url text and loads favicon - mozilla`() {
|
||||||
|
ExceptionsListItemViewHolder(view, interactor, icons)
|
||||||
|
.bind(Exception(), url = "mozilla.org")
|
||||||
|
verify { url.text = "mozilla.org" }
|
||||||
|
verify { icons.loadIntoView(favicon, IconRequest("mozilla.org")) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `sets url text and loads favicon - example`() {
|
||||||
|
ExceptionsListItemViewHolder(view, interactor, icons)
|
||||||
|
.bind(Exception(), url = "https://example.com/icon.svg")
|
||||||
|
verify { url.text = "https://example.com/icon.svg" }
|
||||||
|
verify { icons.loadIntoView(favicon, IconRequest("https://example.com/icon.svg")) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `delete button calls interactor`() {
|
||||||
|
val slot = slot<View.OnClickListener>()
|
||||||
|
val exception = Exception()
|
||||||
|
every { deleteButton.setOnClickListener(capture(slot)) } just Runs
|
||||||
|
ExceptionsListItemViewHolder(view, interactor, icons).bind(exception, url = "mozilla.org")
|
||||||
|
|
||||||
|
every { interactor.onDeleteOne(exception) } just Runs
|
||||||
|
slot.captured.onClick(mockk())
|
||||||
|
verify { interactor.onDeleteOne(exception) }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Exception
|
||||||
|
}
|
|
@ -1,113 +0,0 @@
|
||||||
/* 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.loginexceptions
|
|
||||||
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import androidx.appcompat.view.ContextThemeWrapper
|
|
||||||
import io.mockk.every
|
|
||||||
import io.mockk.mockk
|
|
||||||
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.R
|
|
||||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
|
||||||
import org.mozilla.fenix.loginexceptions.viewholders.LoginExceptionsDeleteButtonViewHolder
|
|
||||||
import org.mozilla.fenix.loginexceptions.viewholders.LoginExceptionsHeaderViewHolder
|
|
||||||
import org.mozilla.fenix.loginexceptions.viewholders.LoginExceptionsListItemViewHolder
|
|
||||||
|
|
||||||
@RunWith(FenixRobolectricTestRunner::class)
|
|
||||||
class LoginExceptionsAdapterTest {
|
|
||||||
|
|
||||||
private lateinit var interactor: LoginExceptionsInteractor
|
|
||||||
private lateinit var adapter: LoginExceptionsAdapter
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
interactor = mockk()
|
|
||||||
adapter = LoginExceptionsAdapter(interactor)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `creates correct view holder type`() {
|
|
||||||
val parent = LinearLayout(ContextThemeWrapper(testContext, R.style.NormalTheme))
|
|
||||||
adapter.updateData(listOf(mockk(), mockk()))
|
|
||||||
assertEquals(4, adapter.itemCount)
|
|
||||||
|
|
||||||
val holders = (0 until adapter.itemCount).asSequence()
|
|
||||||
.map { i -> adapter.getItemViewType(i) }
|
|
||||||
.map { viewType -> adapter.onCreateViewHolder(parent, viewType) }
|
|
||||||
.toList()
|
|
||||||
assertEquals(4, holders.size)
|
|
||||||
|
|
||||||
assertTrue(holders[0] is LoginExceptionsHeaderViewHolder)
|
|
||||||
assertTrue(holders[1] is LoginExceptionsListItemViewHolder)
|
|
||||||
assertTrue(holders[2] is LoginExceptionsListItemViewHolder)
|
|
||||||
assertTrue(holders[3] is LoginExceptionsDeleteButtonViewHolder)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `headers and delete should check if the other object is the same`() {
|
|
||||||
assertTrue(LoginExceptionsAdapter.DiffCallback.areItemsTheSame(
|
|
||||||
LoginExceptionsAdapter.AdapterItem.Header,
|
|
||||||
LoginExceptionsAdapter.AdapterItem.Header
|
|
||||||
))
|
|
||||||
assertTrue(LoginExceptionsAdapter.DiffCallback.areItemsTheSame(
|
|
||||||
LoginExceptionsAdapter.AdapterItem.DeleteButton,
|
|
||||||
LoginExceptionsAdapter.AdapterItem.DeleteButton
|
|
||||||
))
|
|
||||||
assertFalse(LoginExceptionsAdapter.DiffCallback.areItemsTheSame(
|
|
||||||
LoginExceptionsAdapter.AdapterItem.Header,
|
|
||||||
LoginExceptionsAdapter.AdapterItem.DeleteButton
|
|
||||||
))
|
|
||||||
assertTrue(LoginExceptionsAdapter.DiffCallback.areContentsTheSame(
|
|
||||||
LoginExceptionsAdapter.AdapterItem.Header,
|
|
||||||
LoginExceptionsAdapter.AdapterItem.Header
|
|
||||||
))
|
|
||||||
assertTrue(LoginExceptionsAdapter.DiffCallback.areContentsTheSame(
|
|
||||||
LoginExceptionsAdapter.AdapterItem.DeleteButton,
|
|
||||||
LoginExceptionsAdapter.AdapterItem.DeleteButton
|
|
||||||
))
|
|
||||||
assertFalse(LoginExceptionsAdapter.DiffCallback.areContentsTheSame(
|
|
||||||
LoginExceptionsAdapter.AdapterItem.DeleteButton,
|
|
||||||
LoginExceptionsAdapter.AdapterItem.Header
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `items with the same id should be marked as same`() {
|
|
||||||
assertTrue(LoginExceptionsAdapter.DiffCallback.areItemsTheSame(
|
|
||||||
LoginExceptionsAdapter.AdapterItem.Item(mockk {
|
|
||||||
every { id } returns 12L
|
|
||||||
}),
|
|
||||||
LoginExceptionsAdapter.AdapterItem.Item(mockk {
|
|
||||||
every { id } returns 12L
|
|
||||||
})
|
|
||||||
))
|
|
||||||
assertFalse(LoginExceptionsAdapter.DiffCallback.areItemsTheSame(
|
|
||||||
LoginExceptionsAdapter.AdapterItem.Item(mockk {
|
|
||||||
every { id } returns 14L
|
|
||||||
}),
|
|
||||||
LoginExceptionsAdapter.AdapterItem.Item(mockk {
|
|
||||||
every { id } returns 12L
|
|
||||||
})
|
|
||||||
))
|
|
||||||
assertFalse(LoginExceptionsAdapter.DiffCallback.areItemsTheSame(
|
|
||||||
LoginExceptionsAdapter.AdapterItem.Item(mockk {
|
|
||||||
every { id } returns 14L
|
|
||||||
}),
|
|
||||||
LoginExceptionsAdapter.AdapterItem.Header
|
|
||||||
))
|
|
||||||
assertFalse(LoginExceptionsAdapter.DiffCallback.areItemsTheSame(
|
|
||||||
LoginExceptionsAdapter.AdapterItem.DeleteButton,
|
|
||||||
LoginExceptionsAdapter.AdapterItem.Item(mockk {
|
|
||||||
every { id } returns 14L
|
|
||||||
})
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
/* 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.loginexceptions
|
|
||||||
|
|
||||||
import io.mockk.mockk
|
|
||||||
import mozilla.components.feature.logins.exceptions.LoginException
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class LoginExceptionsInteractorTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun onDeleteAll() {
|
|
||||||
var onDeleteAll = false
|
|
||||||
val interactor = LoginExceptionsInteractor(
|
|
||||||
mockk(),
|
|
||||||
{ onDeleteAll = true }
|
|
||||||
)
|
|
||||||
interactor.onDeleteAll()
|
|
||||||
assertEquals(true, onDeleteAll)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun onDeleteOne() {
|
|
||||||
var exceptionsItemReceived: LoginException? = null
|
|
||||||
val exceptionsItem: LoginException = mockk()
|
|
||||||
val interactor = LoginExceptionsInteractor(
|
|
||||||
{ exceptionsItemReceived = exceptionsItem },
|
|
||||||
mockk()
|
|
||||||
)
|
|
||||||
interactor.onDeleteOne(exceptionsItem)
|
|
||||||
assertEquals(exceptionsItemReceived, exceptionsItem)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
/* 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.loginexceptions.viewholders
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.ImageButton
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import io.mockk.Runs
|
|
||||||
import io.mockk.every
|
|
||||||
import io.mockk.just
|
|
||||||
import io.mockk.mockk
|
|
||||||
import io.mockk.slot
|
|
||||||
import io.mockk.verify
|
|
||||||
import mozilla.components.feature.logins.exceptions.LoginException
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import org.mozilla.fenix.R
|
|
||||||
import org.mozilla.fenix.loginexceptions.LoginExceptionsInteractor
|
|
||||||
|
|
||||||
class LoginExceptionsListItemViewHolderTest {
|
|
||||||
|
|
||||||
private lateinit var view: View
|
|
||||||
private lateinit var url: TextView
|
|
||||||
private lateinit var deleteButton: ImageButton
|
|
||||||
private lateinit var interactor: LoginExceptionsInteractor
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
url = mockk(relaxUnitFun = true)
|
|
||||||
deleteButton = mockk(relaxUnitFun = true)
|
|
||||||
view = mockk {
|
|
||||||
every { findViewById<TextView>(R.id.webAddressView) } returns url
|
|
||||||
every { findViewById<ImageButton>(R.id.delete_exception) } returns deleteButton
|
|
||||||
every { findViewById<ImageView>(R.id.favicon_image) } returns mockk()
|
|
||||||
}
|
|
||||||
interactor = mockk()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `sets url text`() {
|
|
||||||
LoginExceptionsListItemViewHolder(view, interactor).bind(mockk {
|
|
||||||
every { origin } returns "mozilla.org"
|
|
||||||
})
|
|
||||||
verify { url.text = "mozilla.org" }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `delete button calls interactor`() {
|
|
||||||
val slot = slot<View.OnClickListener>()
|
|
||||||
val loginException = mockk<LoginException> {
|
|
||||||
every { origin } returns "mozilla.org"
|
|
||||||
}
|
|
||||||
every { deleteButton.setOnClickListener(capture(slot)) } just Runs
|
|
||||||
LoginExceptionsListItemViewHolder(view, interactor).bind(loginException)
|
|
||||||
|
|
||||||
every { interactor.onDeleteOne(loginException) } just Runs
|
|
||||||
slot.captured.onClick(mockk())
|
|
||||||
verify { interactor.onDeleteOne(loginException) }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
/* 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.trackingprotectionexceptions
|
|
||||||
|
|
||||||
import io.mockk.mockk
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.test.runBlockingTest
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
|
||||||
import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsDeleteButtonViewHolder
|
|
||||||
import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsHeaderViewHolder
|
|
||||||
import org.mozilla.fenix.trackingprotectionexceptions.viewholders.ExceptionsListItemViewHolder
|
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
|
||||||
@RunWith(FenixRobolectricTestRunner::class)
|
|
||||||
class ExceptionsAdapterTest {
|
|
||||||
|
|
||||||
private lateinit var interactor: ExceptionsInteractor
|
|
||||||
private lateinit var adapter: ExceptionsAdapter
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
interactor = mockk()
|
|
||||||
adapter = ExceptionsAdapter(interactor)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `binds header and delete button with other adapter items`() = runBlockingTest {
|
|
||||||
adapter.updateData(listOf(mockk(), mockk()))
|
|
||||||
|
|
||||||
assertEquals(4, adapter.itemCount)
|
|
||||||
assertEquals(ExceptionsHeaderViewHolder.LAYOUT_ID, adapter.getItemViewType(0))
|
|
||||||
assertEquals(ExceptionsListItemViewHolder.LAYOUT_ID, adapter.getItemViewType(1))
|
|
||||||
assertEquals(ExceptionsListItemViewHolder.LAYOUT_ID, adapter.getItemViewType(2))
|
|
||||||
assertEquals(ExceptionsDeleteButtonViewHolder.LAYOUT_ID, adapter.getItemViewType(3))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
/* 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.trackingprotectionexceptions
|
|
||||||
|
|
||||||
import io.mockk.mockk
|
|
||||||
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class ExceptionsInteractorTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun onLearnMore() {
|
|
||||||
var learnMoreClicked = false
|
|
||||||
val interactor =
|
|
||||||
ExceptionsInteractor(
|
|
||||||
{ learnMoreClicked = true },
|
|
||||||
mockk(),
|
|
||||||
mockk()
|
|
||||||
)
|
|
||||||
interactor.onLearnMore()
|
|
||||||
assertEquals(true, learnMoreClicked)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun onDeleteAll() {
|
|
||||||
var onDeleteAll = false
|
|
||||||
val interactor =
|
|
||||||
ExceptionsInteractor(
|
|
||||||
mockk(),
|
|
||||||
mockk(),
|
|
||||||
{ onDeleteAll = true }
|
|
||||||
)
|
|
||||||
interactor.onDeleteAll()
|
|
||||||
assertEquals(true, onDeleteAll)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun onDeleteOne() {
|
|
||||||
var exceptionsItemReceived: TrackingProtectionException? = null
|
|
||||||
val exceptionsItem =
|
|
||||||
ExceptionItem("url")
|
|
||||||
val interactor =
|
|
||||||
ExceptionsInteractor(
|
|
||||||
mockk(),
|
|
||||||
{ exceptionsItemReceived = exceptionsItem },
|
|
||||||
mockk()
|
|
||||||
)
|
|
||||||
interactor.onDeleteOne(exceptionsItem)
|
|
||||||
assertEquals(exceptionsItemReceived, exceptionsItem)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
/* 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.trackingprotectionexceptions
|
|
||||||
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Assert.assertNotSame
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class TrackingProtectionExceptionsFragmentStoreTest {
|
|
||||||
@Test
|
|
||||||
fun onChange() = runBlocking {
|
|
||||||
val initialState = emptyDefaultState()
|
|
||||||
val store =
|
|
||||||
ExceptionsFragmentStore(
|
|
||||||
initialState
|
|
||||||
)
|
|
||||||
val newExceptionsItem =
|
|
||||||
ExceptionItem("URL")
|
|
||||||
|
|
||||||
store.dispatch(ExceptionsFragmentAction.Change(listOf(newExceptionsItem))).join()
|
|
||||||
assertNotSame(initialState, store.state)
|
|
||||||
assertEquals(
|
|
||||||
store.state.items,
|
|
||||||
listOf(newExceptionsItem)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun emptyDefaultState(): ExceptionsFragmentState =
|
|
||||||
ExceptionsFragmentState(
|
|
||||||
items = listOf()
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
/* 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.trackingprotectionexceptions.viewholders
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import androidx.appcompat.view.ContextThemeWrapper
|
|
||||||
import io.mockk.mockk
|
|
||||||
import io.mockk.verify
|
|
||||||
import kotlinx.android.synthetic.main.delete_exceptions_button.view.*
|
|
||||||
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.trackingprotectionexceptions.ExceptionsInteractor
|
|
||||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
|
||||||
|
|
||||||
@RunWith(FenixRobolectricTestRunner::class)
|
|
||||||
class ExceptionsDeleteButtonViewHolderTest {
|
|
||||||
|
|
||||||
private lateinit var view: View
|
|
||||||
private lateinit var interactor: ExceptionsInteractor
|
|
||||||
private lateinit var viewHolder: ExceptionsDeleteButtonViewHolder
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
val appCompatContext = ContextThemeWrapper(testContext, R.style.NormalTheme)
|
|
||||||
view = LayoutInflater.from(appCompatContext)
|
|
||||||
.inflate(ExceptionsDeleteButtonViewHolder.LAYOUT_ID, null)
|
|
||||||
interactor = mockk(relaxed = true)
|
|
||||||
viewHolder = ExceptionsDeleteButtonViewHolder(view, interactor)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `calls onDeleteAll on click`() {
|
|
||||||
view.removeAllExceptions.performClick()
|
|
||||||
|
|
||||||
verify { interactor.onDeleteAll() }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
/* 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.trackingprotectionexceptions.viewholders
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import io.mockk.mockk
|
|
||||||
import io.mockk.verify
|
|
||||||
import kotlinx.android.synthetic.main.exception_item.view.*
|
|
||||||
import mozilla.components.concept.engine.content.blocking.TrackingProtectionException
|
|
||||||
import mozilla.components.support.test.robolectric.testContext
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.mozilla.fenix.trackingprotectionexceptions.ExceptionsInteractor
|
|
||||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
|
||||||
|
|
||||||
@RunWith(FenixRobolectricTestRunner::class)
|
|
||||||
class ExceptionsListItemViewHolderTest {
|
|
||||||
|
|
||||||
private lateinit var view: View
|
|
||||||
private lateinit var interactor: ExceptionsInteractor
|
|
||||||
private lateinit var viewHolder: ExceptionsListItemViewHolder
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setup() {
|
|
||||||
view = LayoutInflater.from(testContext)
|
|
||||||
.inflate(ExceptionsListItemViewHolder.LAYOUT_ID, null)
|
|
||||||
interactor = mockk(relaxed = true)
|
|
||||||
viewHolder = ExceptionsListItemViewHolder(view, interactor)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `bind url and icon`() {
|
|
||||||
val exception = object : TrackingProtectionException {
|
|
||||||
override val url = "https://example.com/icon.svg"
|
|
||||||
}
|
|
||||||
viewHolder.bind(exception)
|
|
||||||
|
|
||||||
assertEquals(exception.url, view.webAddressView.text)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `calls onDeleteOne on click`() {
|
|
||||||
val exception = object : TrackingProtectionException {
|
|
||||||
override val url = "https://example.com/icon.svg"
|
|
||||||
}
|
|
||||||
viewHolder.bind(exception)
|
|
||||||
view.delete_exception.performClick()
|
|
||||||
|
|
||||||
verify { interactor.onDeleteOne(exception) }
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue