Loading app/src/main/java/org/mozilla/fenix/HomeActivity.kt +1 −1 Original line number Diff line number Diff line Loading @@ -67,6 +67,7 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder 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.components import org.mozilla.fenix.ext.metrics Loading Loading @@ -98,7 +99,6 @@ import org.mozilla.fenix.sync.SyncedTabsFragmentDirections import org.mozilla.fenix.tabtray.TabTrayDialogFragment import org.mozilla.fenix.theme.DefaultThemeManager import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.trackingprotectionexceptions.TrackingProtectionExceptionsFragmentDirections import org.mozilla.fenix.utils.BrowsersCache /** Loading app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsAdapter.kt→app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt +100 −0 Original line number Diff line number Diff line Loading @@ -2,82 +2,99 @@ * 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 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 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 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, * Adapter for a list of sites that are exempted from saving logins or tracking protection, * along with controls to remove the exception. */ class LoginExceptionsAdapter( private val interactor: LoginExceptionsInteractor ) : ListAdapter<LoginExceptionsAdapter.AdapterItem, RecyclerView.ViewHolder>(DiffCallback) { 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<LoginException>) { fun updateData(exceptions: List<T>) { val adapterItems: List<AdapterItem> = listOf(AdapterItem.Header) + exceptions.map { AdapterItem.Item(it) } + exceptions.map { wrapAdapterItem(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 /** * 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 } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { final 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 ) deleteButtonLayoutId -> ExceptionsDeleteButtonViewHolder(view, interactor) ExceptionsHeaderViewHolder.LAYOUT_ID -> ExceptionsHeaderViewHolder(view, headerDescriptionResource) ExceptionsListItemViewHolder.LAYOUT_ID -> ExceptionsListItemViewHolder(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) @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() 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 /** * 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 } @Suppress("DiffUtilEquals") override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = oldItem == newItem } } app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsHeaderViewHolder.kt→app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsInteractor.kt +21 −0 Original line number Diff line number Diff line Loading @@ -2,16 +2,20 @@ * 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 package org.mozilla.fenix.exceptions import android.view.View import androidx.recyclerview.widget.RecyclerView import org.mozilla.fenix.R /** * 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() class ExceptionsHeaderViewHolder( view: View ) : RecyclerView.ViewHolder(view) { companion object { const val LAYOUT_ID = R.layout.exceptions_description } /** * Called whenever one exception item is deleted */ fun onDeleteOne(item: T) } app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsView.kt→app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsView.kt +41 −0 Original line number Diff line number Diff line Loading @@ -2,7 +2,7 @@ * 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 package org.mozilla.fenix.exceptions import android.view.LayoutInflater import android.view.ViewGroup Loading @@ -11,52 +11,31 @@ 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( abstract class ExceptionsView<T : Any>( container: ViewGroup, val interactor: LoginExceptionsInteractor 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) private val exceptionsAdapter = LoginExceptionsAdapter(interactor) protected abstract val exceptionsAdapter: ExceptionsAdapter<T> 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) fun update(items: List<T>) { exceptions_empty_view.isVisible = items.isEmpty() exceptions_list.isVisible = items.isNotEmpty() exceptionsAdapter.updateData(items) } } app/src/main/java/org/mozilla/fenix/loginexceptions/ExceptionsFragmentStore.kt→app/src/main/java/org/mozilla/fenix/exceptions/login/ExceptionsFragmentStore.kt +2 −2 Original line number Diff line number Diff line Loading @@ -2,7 +2,7 @@ * 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 package org.mozilla.fenix.exceptions.login import mozilla.components.feature.logins.exceptions.LoginException import mozilla.components.lib.state.Action Loading @@ -26,7 +26,7 @@ sealed class ExceptionsFragmentAction : Action { * The state for the Exceptions Screen * @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. Loading Loading
app/src/main/java/org/mozilla/fenix/HomeActivity.kt +1 −1 Original line number Diff line number Diff line Loading @@ -67,6 +67,7 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder 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.components import org.mozilla.fenix.ext.metrics Loading Loading @@ -98,7 +99,6 @@ import org.mozilla.fenix.sync.SyncedTabsFragmentDirections import org.mozilla.fenix.tabtray.TabTrayDialogFragment import org.mozilla.fenix.theme.DefaultThemeManager import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.trackingprotectionexceptions.TrackingProtectionExceptionsFragmentDirections import org.mozilla.fenix.utils.BrowsersCache /** Loading
app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsAdapter.kt→app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt +100 −0 Original line number Diff line number Diff line Loading @@ -2,82 +2,99 @@ * 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 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 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 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, * Adapter for a list of sites that are exempted from saving logins or tracking protection, * along with controls to remove the exception. */ class LoginExceptionsAdapter( private val interactor: LoginExceptionsInteractor ) : ListAdapter<LoginExceptionsAdapter.AdapterItem, RecyclerView.ViewHolder>(DiffCallback) { 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<LoginException>) { fun updateData(exceptions: List<T>) { val adapterItems: List<AdapterItem> = listOf(AdapterItem.Header) + exceptions.map { AdapterItem.Item(it) } + exceptions.map { wrapAdapterItem(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 /** * 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 } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { final 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 ) deleteButtonLayoutId -> ExceptionsDeleteButtonViewHolder(view, interactor) ExceptionsHeaderViewHolder.LAYOUT_ID -> ExceptionsHeaderViewHolder(view, headerDescriptionResource) ExceptionsListItemViewHolder.LAYOUT_ID -> ExceptionsListItemViewHolder(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) @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() 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 /** * 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 } @Suppress("DiffUtilEquals") override fun areContentsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = oldItem == newItem } }
app/src/main/java/org/mozilla/fenix/trackingprotectionexceptions/viewholders/ExceptionsHeaderViewHolder.kt→app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsInteractor.kt +21 −0 Original line number Diff line number Diff line Loading @@ -2,16 +2,20 @@ * 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 package org.mozilla.fenix.exceptions import android.view.View import androidx.recyclerview.widget.RecyclerView import org.mozilla.fenix.R /** * 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() class ExceptionsHeaderViewHolder( view: View ) : RecyclerView.ViewHolder(view) { companion object { const val LAYOUT_ID = R.layout.exceptions_description } /** * Called whenever one exception item is deleted */ fun onDeleteOne(item: T) }
app/src/main/java/org/mozilla/fenix/loginexceptions/LoginExceptionsView.kt→app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsView.kt +41 −0 Original line number Diff line number Diff line Loading @@ -2,7 +2,7 @@ * 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 package org.mozilla.fenix.exceptions import android.view.LayoutInflater import android.view.ViewGroup Loading @@ -11,52 +11,31 @@ 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( abstract class ExceptionsView<T : Any>( container: ViewGroup, val interactor: LoginExceptionsInteractor 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) private val exceptionsAdapter = LoginExceptionsAdapter(interactor) protected abstract val exceptionsAdapter: ExceptionsAdapter<T> 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) fun update(items: List<T>) { exceptions_empty_view.isVisible = items.isEmpty() exceptions_list.isVisible = items.isNotEmpty() exceptionsAdapter.updateData(items) } }
app/src/main/java/org/mozilla/fenix/loginexceptions/ExceptionsFragmentStore.kt→app/src/main/java/org/mozilla/fenix/exceptions/login/ExceptionsFragmentStore.kt +2 −2 Original line number Diff line number Diff line Loading @@ -2,7 +2,7 @@ * 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 package org.mozilla.fenix.exceptions.login import mozilla.components.feature.logins.exceptions.LoginException import mozilla.components.lib.state.Action Loading @@ -26,7 +26,7 @@ sealed class ExceptionsFragmentAction : Action { * The state for the Exceptions Screen * @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. Loading