For #8765: Use shared list widget in exceptions (#14113)

* For #8765: Add resource for shared list widget

* For #8765: Use shared list widget in exceptions
pull/35/head
Tiger Oakes 4 years ago committed by GitHub
parent ac2d65cc5e
commit bb3fd4eb16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -11,6 +11,7 @@ import androidx.annotation.StringRes
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import mozilla.components.ui.widgets.WidgetSiteItemView
import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder
import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder
import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder
@ -67,7 +68,7 @@ abstract class ExceptionsAdapter<T : Any>(
ExceptionsHeaderViewHolder.LAYOUT_ID -> ExceptionsHeaderViewHolder.LAYOUT_ID ->
ExceptionsHeaderViewHolder(view, headerDescriptionResource) ExceptionsHeaderViewHolder(view, headerDescriptionResource)
ExceptionsListItemViewHolder.LAYOUT_ID -> ExceptionsListItemViewHolder.LAYOUT_ID ->
ExceptionsListItemViewHolder(view, interactor) ExceptionsListItemViewHolder(view as WidgetSiteItemView, interactor)
else -> throw IllegalStateException() else -> throw IllegalStateException()
} }
} }

@ -4,39 +4,41 @@
package org.mozilla.fenix.exceptions.viewholders package org.mozilla.fenix.exceptions.viewholders
import android.view.View import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.exception_item.*
import mozilla.components.browser.icons.BrowserIcons import mozilla.components.browser.icons.BrowserIcons
import mozilla.components.ui.widgets.WidgetSiteItemView
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.exceptions.ExceptionsInteractor import org.mozilla.fenix.exceptions.ExceptionsInteractor
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.loadIntoView 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. * View holder for a single website that is exempted from Tracking Protection or Logins.
*/ */
class ExceptionsListItemViewHolder<T : Any>( class ExceptionsListItemViewHolder<T : Any>(
view: View, private val view: WidgetSiteItemView,
private val interactor: ExceptionsInteractor<T>, private val interactor: ExceptionsInteractor<T>,
private val icons: BrowserIcons = view.context.components.core.icons private val icons: BrowserIcons = view.context.components.core.icons
) : ViewHolder(view) { ) : RecyclerView.ViewHolder(view) {
private lateinit var item: T private lateinit var item: T
init { init {
delete_exception.setOnClickListener { view.setSecondaryButton(
icon = R.drawable.ic_close,
contentDescription = R.string.history_delete_item
) {
interactor.onDeleteOne(item) interactor.onDeleteOne(item)
} }
} }
fun bind(item: T, url: String) { fun bind(item: T, url: String) {
this.item = item this.item = item
webAddressView.text = url view.setText(label = url, caption = null)
icons.loadIntoView(favicon_image, url) icons.loadIntoView(view.iconView, url)
} }
companion object { companion object {
const val LAYOUT_ID = R.layout.exception_item const val LAYOUT_ID = R.layout.site_list_item
} }
} }

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<mozilla.components.ui.widgets.WidgetSiteItemView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/site_list_item"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

@ -7,6 +7,7 @@
<color name="primary_text_normal_theme">@color/primary_text_dark_theme</color> <color name="primary_text_normal_theme">@color/primary_text_dark_theme</color>
<color name="secondary_text_normal_theme">@color/secondary_text_dark_theme</color> <color name="secondary_text_normal_theme">@color/secondary_text_dark_theme</color>
<color name="contrast_text_normal_theme">@color/contrast_text_dark_theme</color> <color name="contrast_text_normal_theme">@color/contrast_text_dark_theme</color>
<color name="caption_text_normal_theme">@color/caption_text_dark_theme</color>
<color name="foundation_normal_theme">@color/foundation_dark_theme</color> <color name="foundation_normal_theme">@color/foundation_dark_theme</color>
<color name="above_normal_theme">@color/above_dark_theme</color> <color name="above_normal_theme">@color/above_dark_theme</color>
<color name="inset_normal_theme">@color/inset_dark_theme</color> <color name="inset_normal_theme">@color/inset_dark_theme</color>

@ -16,6 +16,7 @@
<color name="primary_text_light_theme">#20123A</color> <color name="primary_text_light_theme">#20123A</color>
<color name="secondary_text_light_theme">@color/photonGrey50</color> <color name="secondary_text_light_theme">@color/photonGrey50</color>
<color name="contrast_text_light_theme">@color/primary_text_dark_theme</color> <color name="contrast_text_light_theme">@color/primary_text_dark_theme</color>
<color name="caption_text_light_theme">@color/photonLightGrey90</color>
<color name="foundation_light_theme">#F9F9FB</color> <color name="foundation_light_theme">#F9F9FB</color>
<color name="inset_light_theme">#E0E0E6</color> <color name="inset_light_theme">#E0E0E6</color>
<color name="above_light_theme">#FFF</color> <color name="above_light_theme">#FFF</color>
@ -85,6 +86,7 @@
<color name="primary_text_dark_theme">#FBFBFE</color> <color name="primary_text_dark_theme">#FBFBFE</color>
<color name="secondary_text_dark_theme">#A7A2B7</color> <color name="secondary_text_dark_theme">#A7A2B7</color>
<color name="contrast_text_dark_theme">@color/primary_text_dark_theme</color> <color name="contrast_text_dark_theme">@color/primary_text_dark_theme</color>
<color name="caption_text_dark_theme">@color/photonLightGrey70</color>
<color name="foundation_dark_theme">#1C1B22</color> <color name="foundation_dark_theme">#1C1B22</color>
<color name="inset_dark_theme">#32313C</color> <color name="inset_dark_theme">#32313C</color>
<color name="above_dark_theme">#32313C</color> <color name="above_dark_theme">#32313C</color>
@ -151,6 +153,7 @@
<color name="primary_text_private_theme">#FBFBFE</color> <color name="primary_text_private_theme">#FBFBFE</color>
<color name="secondary_text_private_theme">#A7A2B7</color> <color name="secondary_text_private_theme">#A7A2B7</color>
<color name="contrast_text_private_theme">@color/primary_text_private_theme</color> <color name="contrast_text_private_theme">@color/primary_text_private_theme</color>
<color name="caption_text_private_theme">@color/photonLightGrey70</color>
<color name="foundation_private_theme">#261E4B</color> <color name="foundation_private_theme">#261E4B</color>
<color name="inset_private_theme">@color/photonInk50</color> <color name="inset_private_theme">@color/photonInk50</color>
<color name="above_private_theme">@color/photonInk50</color> <color name="above_private_theme">@color/photonInk50</color>
@ -209,6 +212,7 @@
<color name="primary_text_normal_theme">@color/primary_text_light_theme</color> <color name="primary_text_normal_theme">@color/primary_text_light_theme</color>
<color name="secondary_text_normal_theme">@color/secondary_text_light_theme</color> <color name="secondary_text_normal_theme">@color/secondary_text_light_theme</color>
<color name="contrast_text_normal_theme">@color/contrast_text_light_theme</color> <color name="contrast_text_normal_theme">@color/contrast_text_light_theme</color>
<color name="caption_text_normal_theme">@color/caption_text_light_theme</color>
<color name="foundation_normal_theme">@color/foundation_light_theme</color> <color name="foundation_normal_theme">@color/foundation_light_theme</color>
<color name="above_normal_theme">@color/above_light_theme</color> <color name="above_normal_theme">@color/above_light_theme</color>
<color name="inset_normal_theme">@color/inset_light_theme</color> <color name="inset_normal_theme">@color/inset_light_theme</color>

@ -81,6 +81,8 @@
<item name="awesomeBarIndicatorBookmarkColor">@color/search_suggestion_indicator_icon_bookmark_color_normal_theme</item> <item name="awesomeBarIndicatorBookmarkColor">@color/search_suggestion_indicator_icon_bookmark_color_normal_theme</item>
<!-- Shared widget colors --> <!-- Shared widget colors -->
<item name="mozac_primary_text_color">@color/primary_text_normal_theme</item>
<item name="mozac_caption_text_color">@color/caption_text_normal_theme</item>
<item name="mozac_widget_favicon_background_color">@color/mozac_widget_favicon_background_normal_theme</item> <item name="mozac_widget_favicon_background_color">@color/mozac_widget_favicon_background_normal_theme</item>
<item name="mozac_widget_favicon_border_color">@color/mozac_widget_favicon_border_normal_theme</item> <item name="mozac_widget_favicon_border_color">@color/mozac_widget_favicon_border_normal_theme</item>
@ -221,6 +223,8 @@
<item name="awesomeBarIndicatorBookmarkColor">@color/search_suggestion_indicator_icon_bookmark_color_dark_theme</item> <item name="awesomeBarIndicatorBookmarkColor">@color/search_suggestion_indicator_icon_bookmark_color_dark_theme</item>
<!-- Shared widget colors --> <!-- Shared widget colors -->
<item name="mozac_primary_text_color">@color/primary_text_private_theme</item>
<item name="mozac_caption_text_color">@color/caption_text_private_theme</item>
<item name="mozac_widget_favicon_background_color">@color/mozac_widget_favicon_background_private_theme</item> <item name="mozac_widget_favicon_background_color">@color/mozac_widget_favicon_background_private_theme</item>
<item name="mozac_widget_favicon_border_color">@color/mozac_widget_favicon_border_private_theme</item> <item name="mozac_widget_favicon_border_color">@color/mozac_widget_favicon_border_private_theme</item>

@ -5,9 +5,6 @@
package org.mozilla.fenix.exceptions.viewholders package org.mozilla.fenix.exceptions.viewholders
import android.view.View import android.view.View
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import io.mockk.MockKAnnotations import io.mockk.MockKAnnotations
import io.mockk.Runs import io.mockk.Runs
import io.mockk.every import io.mockk.every
@ -18,17 +15,14 @@ import io.mockk.slot
import io.mockk.verify import io.mockk.verify
import mozilla.components.browser.icons.BrowserIcons import mozilla.components.browser.icons.BrowserIcons
import mozilla.components.browser.icons.IconRequest import mozilla.components.browser.icons.IconRequest
import mozilla.components.ui.widgets.WidgetSiteItemView
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.exceptions.ExceptionsInteractor import org.mozilla.fenix.exceptions.ExceptionsInteractor
class ExceptionsListItemViewHolderTest { class ExceptionsListItemViewHolderTest {
@MockK private lateinit var view: View @MockK(relaxed = true) private lateinit var view: WidgetSiteItemView
@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 icons: BrowserIcons
@MockK private lateinit var interactor: ExceptionsInteractor<Exception> @MockK private lateinit var interactor: ExceptionsInteractor<Exception>
@ -36,37 +30,34 @@ class ExceptionsListItemViewHolderTest {
fun setup() { fun setup() {
MockKAnnotations.init(this) MockKAnnotations.init(this)
every { view.findViewById<TextView>(R.id.webAddressView) } returns url every { icons.loadIntoView(view.iconView, any()) } returns mockk()
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 @Test
fun `sets url text and loads favicon - mozilla`() { fun `sets url text and loads favicon - mozilla`() {
ExceptionsListItemViewHolder(view, interactor, icons) ExceptionsListItemViewHolder(view, interactor, icons)
.bind(Exception(), url = "mozilla.org") .bind(Exception(), url = "mozilla.org")
verify { url.text = "mozilla.org" } verify { view.setText(label = "mozilla.org", caption = null) }
verify { icons.loadIntoView(favicon, IconRequest("mozilla.org")) } verify { icons.loadIntoView(view.iconView, IconRequest("mozilla.org")) }
} }
@Test @Test
fun `sets url text and loads favicon - example`() { fun `sets url text and loads favicon - example`() {
ExceptionsListItemViewHolder(view, interactor, icons) ExceptionsListItemViewHolder(view, interactor, icons)
.bind(Exception(), url = "https://example.com/icon.svg") .bind(Exception(), url = "https://example.com/icon.svg")
verify { url.text = "https://example.com/icon.svg" } verify { view.setText(label = "https://example.com/icon.svg", caption = null) }
verify { icons.loadIntoView(favicon, IconRequest("https://example.com/icon.svg")) } verify { icons.loadIntoView(view.iconView, IconRequest("https://example.com/icon.svg")) }
} }
@Test @Test
fun `delete button calls interactor`() { fun `delete button calls interactor`() {
val slot = slot<View.OnClickListener>() val slot = slot<(View) -> Unit>()
val exception = Exception() val exception = Exception()
every { deleteButton.setOnClickListener(capture(slot)) } just Runs every { view.setSecondaryButton(any(), any<Int>(), capture(slot)) } just Runs
ExceptionsListItemViewHolder(view, interactor, icons).bind(exception, url = "mozilla.org") ExceptionsListItemViewHolder(view, interactor, icons).bind(exception, url = "mozilla.org")
every { interactor.onDeleteOne(exception) } just Runs every { interactor.onDeleteOne(exception) } just Runs
slot.captured.onClick(mockk()) slot.captured.invoke(mockk())
verify { interactor.onDeleteOne(exception) } verify { interactor.onDeleteOne(exception) }
} }

Loading…
Cancel
Save