parent
8b923fc7a4
commit
1d28f63737
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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() }
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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) }
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
@ -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)) }
|
||||
}
|
||||
}
|
@ -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