Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Commit

Permalink
Merge #7900
Browse files Browse the repository at this point in the history
7900: For mozilla-mobile/fenix#8765: Add site item widget r=gabrielluong a=NotWoods

`WidgetSiteItemView` is used to show a website inside bookmarks, history, collections, and other lists. Has the following API:
- `iconView` getter to load favicons into the view
- `setText(label, caption)` to set the text shown. If caption is null, the caption text view is hidden and the label is centered.
- `addIconOverlay` inserts other views above the `iconView`, such as a checkmark.
- `setSecondaryButton(icon, contentDescription, onClickListener)` will show an icon button with the given icon and content description. `onClickListener` is called when the button is clicked.
- `removeSecondaryButton` hides the button if it was previously shown by `setSecondaryButton`.



Co-authored-by: Tiger Oakes <[email protected]>
  • Loading branch information
MozLando and NotWoods committed Aug 21, 2020
2 parents d38ffae + 6fc3914 commit 0fbcf31
Show file tree
Hide file tree
Showing 9 changed files with 326 additions and 0 deletions.
13 changes: 13 additions & 0 deletions components/ui/widgets/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
compileSdkVersion config.compileSdkVersion

defaultConfig {
minSdkVersion config.minSdkVersion
targetSdkVersion config.targetSdkVersion

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
Expand All @@ -21,11 +24,21 @@ android {
}

dependencies {
implementation Dependencies.kotlin_stdlib

implementation project(':ui-colors')
implementation project(':ui-icons')

implementation Dependencies.androidx_appcompat
implementation Dependencies.androidx_constraintlayout
implementation Dependencies.androidx_core_ktx
implementation Dependencies.google_material

testImplementation project(":support-test")

testImplementation Dependencies.androidx_test_junit
testImplementation Dependencies.testing_robolectric
testImplementation Dependencies.testing_mockito
}

apply from: '../../../publish.gradle'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.ui.widgets

import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.appcompat.content.res.AppCompatResources.getDrawable
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible

/**
* Shared UI widget for showing a website in a list of websites,
* such as in bookmarks, history, site exceptions, or collections.
*/
class WidgetSiteItemView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {

private val labelView: TextView by lazy { findViewById<TextView>(R.id.label) }
private val captionView: TextView by lazy { findViewById<TextView>(R.id.caption) }
private val iconWrapper: FrameLayout by lazy { findViewById<FrameLayout>(R.id.favicon_wrapper) }
private val secondaryButton: ImageButton by lazy { findViewById<ImageButton>(R.id.secondary_button) }

/**
* ImageView that should display favicons.
*/
val iconView: ImageView by lazy { findViewById<ImageView>(R.id.favicon) }

init {
LayoutInflater.from(context).inflate(R.layout.mozac_widget_site_item, this, true)
}

/**
* Sets the text displayed inside of the site item view.
*
* @param label Main label text, such as a site title.
* @param caption Sub caption text, such as a URL. If null, the caption is hidden.
*/
fun setText(label: CharSequence, caption: CharSequence?) {
labelView.text = label
captionView.text = caption
captionView.isVisible = caption != null
}

/**
* Add a view that will overlay the favicon, such as a checkmark.
*/
fun addIconOverlay(overlay: View) {
iconWrapper.addView(overlay)
}

/**
* Add a secondary button, such as an overflow menu.
*
* @param icon Drawable to display in the button.
* @param contentDescription Accessible description of the button's purpose.
* @param onClickListener Listener called when the button is clicked.
*/
fun setSecondaryButton(
icon: Drawable?,
contentDescription: CharSequence,
onClickListener: (View) -> Unit
) {
secondaryButton.isVisible = true
secondaryButton.setImageDrawable(icon)
secondaryButton.contentDescription = contentDescription
secondaryButton.setOnClickListener(onClickListener)
}

/**
* Add a secondary button, such as an overflow menu.
*
* @param icon Drawable to display in the button.
* @param contentDescription Accessible description of the button's purpose.
* @param onClickListener Listener called when the button is clicked.
*/
fun setSecondaryButton(
@DrawableRes icon: Int,
@StringRes contentDescription: Int,
onClickListener: (View) -> Unit
) = setSecondaryButton(
icon = getDrawable(context, icon),
contentDescription = context.getString(contentDescription),
onClickListener = onClickListener
)

/**
* Removes the secondary button if it was previously set in [setSecondaryButton].
*/
fun removeSecondaryButton() {
secondaryButton.isVisible = false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/mozac_widget_site_item_height"
android:background="?android:attr/selectableItemBackground">

<FrameLayout
android:id="@+id/favicon_wrapper"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintHorizontal_chainStyle="spread_inside"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/label">
<ImageView
android:id="@+id/favicon"
style="@style/Mozac.Widgets.Favicon"
android:importantForAccessibility="no"
tools:src="@android:drawable/ic_secure" />
</FrameLayout>

<TextView
android:id="@+id/label"
style="@style/Mozac.Widgets.SiteItem.Label"
android:layout_width="0dp"
tools:textColor="#20123A"
tools:text="Example site"
app:layout_goneMarginEnd="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/caption"
app:layout_constraintStart_toEndOf="@id/favicon_wrapper"
app:layout_constraintEnd_toStartOf="@id/secondary_button"
app:layout_constraintVertical_chainStyle="packed" />

<TextView
android:id="@+id/caption"
style="@style/Mozac.Widgets.SiteItem.Caption"
android:layout_width="0dp"
android:layout_marginTop="2dp"
tools:text="https://example.com/"
tools:textColor="@color/photonLightGrey90"
app:layout_constraintEnd_toEndOf="@id/label"
app:layout_constraintStart_toStartOf="@id/label"
app:layout_constraintTop_toBottomOf="@id/label"
app:layout_constraintBottom_toBottomOf="parent" />

<ImageButton
android:id="@+id/secondary_button"
android:layout_width="@dimen/mozac_widget_site_item_secondary_button_size"
android:layout_height="@dimen/mozac_widget_site_item_secondary_button_size"
android:padding="@dimen/mozac_widget_site_item_secondary_button_padding"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:visibility="gone"
tools:visibility="visible"
tools:src="@drawable/mozac_ic_menu"
tools:ignore="ContentDescription"
tools:tint="#20123A"
app:tint="?attr/mozac_primary_text_color"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/label"
app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
7 changes: 7 additions & 0 deletions components/ui/widgets/src/main/res/values/attrs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
<attr name="mozac_accent" format="reference" />
<attr name="mozac_contrast_text" format="reference" />

<!-- Background color for favicon widget box -->
<attr name="mozac_widget_favicon_background_color" format="reference" />
<!-- Border color for favicon widget box -->
<attr name="mozac_widget_favicon_border_color" format="reference" />

<!-- Label color and icon button tint for site item widget -->
<attr name="mozac_primary_text_color" format="reference" />
<!-- Caption color for site item widget -->
<attr name="mozac_caption_text_color" format="reference" />
</resources>
6 changes: 6 additions & 0 deletions components/ui/widgets/src/main/res/values/dimens.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,10 @@
<resources>
<dimen name="mozac_widget_favicon_size">40dp</dimen>
<dimen name="mozac_widget_favicon_padding">8dp</dimen>

<dimen name="mozac_widget_site_item_height">56dp</dimen>
<dimen name="mozac_widget_site_item_label_text_size">16sp</dimen>
<dimen name="mozac_widget_site_item_caption_text_size">12sp</dimen>
<dimen name="mozac_widget_site_item_secondary_button_size">32dp</dimen>
<dimen name="mozac_widget_site_item_secondary_button_padding">4dp</dimen>
</resources>
19 changes: 19 additions & 0 deletions components/ui/widgets/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="Mozac.Widgets.TestTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="mozac_widget_favicon_background_color">@color/photonWhite</item>
<item name="mozac_widget_favicon_border_color">@color/photonLightGrey30</item>
<item name="mozac_primary_text_color">@color/photonInk90</item>
<item name="mozac_caption_text_color">@color/photonInk50</item>
</style>

<!-- Button styling -->
<style name="Mozac.Widgets.NeutralButton" parent="Widget.MaterialComponents.Button.TextButton">
Expand Down Expand Up @@ -41,4 +47,17 @@
<item name="android:padding">@dimen/mozac_widget_favicon_padding</item>
<item name="android:background">@drawable/mozac_widget_favicon_background</item>
</style>

<style name="Mozac.Widgets.SiteItem.Label" parent="">
<item name="android:layout_height">wrap_content</item>
<item name="android:ellipsize">end</item>
<item name="android:singleLine">true</item>
<item name="android:textAlignment">viewStart</item>
<item name="android:textSize">@dimen/mozac_widget_site_item_label_text_size</item>
<item name="android:textColor">?attr/mozac_primary_text_color</item>
</style>
<style name="Mozac.Widgets.SiteItem.Caption" parent="Mozac.Widgets.SiteItem.Label">
<item name="android:textSize">@dimen/mozac_widget_site_item_caption_text_size</item>
<item name="android:textColor">?attr/mozac_caption_text_color</item>
</style>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/* 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 mozilla.components.ui.widgets

import android.graphics.drawable.Drawable
import android.widget.ImageButton
import android.widget.TextView
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.support.test.mock
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class WidgetSiteItemViewTest {

private lateinit var view: WidgetSiteItemView

@Before
fun setup() {
val context = ContextThemeWrapper(testContext, R.style.Mozac_Widgets_TestTheme)
view = WidgetSiteItemView(context)
}

@Test
fun `setText hides the caption`() {
val labelView = view.findViewById<TextView>(R.id.label)
val captionView = view.findViewById<TextView>(R.id.caption)

view.setText(label = "label", caption = null)
assertEquals("label", labelView.text)
assertTrue(captionView.isGone)

view.setText(label = "Label", caption = "")
assertEquals("Label", labelView.text)
assertEquals("", captionView.text)
assertTrue(captionView.isVisible)
}

@Test
fun `setSecondaryButton shows the button`() {
val secondaryButton = view.findViewById<ImageButton>(R.id.secondary_button)
val drawable = mock<Drawable>()
var clicked = false
view.setSecondaryButton(
icon = drawable,
contentDescription = "Menu",
onClickListener = { clicked = true }
)
assertTrue(secondaryButton.isVisible)
assertEquals(drawable, secondaryButton.drawable)
assertEquals("Menu", secondaryButton.contentDescription)

secondaryButton.performClick()
assertTrue(clicked)
}

@Test
fun `setSecondaryButton with resource IDs shows the button`() {
val secondaryButton = view.findViewById<ImageButton>(R.id.secondary_button)
var clicked = false
view.setSecondaryButton(
icon = R.drawable.mozac_ic_lock,
contentDescription = R.string.mozac_error_lock,
onClickListener = { clicked = true }
)
assertTrue(secondaryButton.isVisible)
assertNotNull(secondaryButton.drawable)
assertEquals("mozac_error_lock", secondaryButton.contentDescription)

secondaryButton.performClick()
assertTrue(clicked)
}

@Test
fun `removeSecondaryButton does nothing if set was not called`() {
val secondaryButton = view.findViewById<ImageButton>(R.id.secondary_button)
assertTrue(secondaryButton.isGone)

view.removeSecondaryButton()
assertTrue(secondaryButton.isGone)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sdk=28
6 changes: 6 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ permalink: /changelog/
* **browser-menu2**
* Added `style` parameter to `BrowserMenuController`.

* **ui-widgets**
* Added widget for showing a website in a list, such as in bookmarks or history. The `mozac_primary_text_color` and `mozac_caption_text_color` attributes should be set.

# 55.0.0

* [Commits](https://github.com/mozilla-mobile/android-components/compare/v54.0.0...v55.0.0)
Expand Down Expand Up @@ -144,6 +147,9 @@ permalink: /changelog/
* Do not show the "Download link" option for html URLs.
* Uses a speculative check, may not work in all cases.

* **ui-widgets**
* Added shared ImageView style for favicons. The `mozac_widget_favicon_background_color` and `mozac_widget_favicon_border_color` attributes should be set, then `style="@style/Mozac.Widgets.Favicon"` can be added to an ImageView.

# 52.0.0

* [Commits](https://github.com/mozilla-mobile/android-components/compare/v51.0.0...v52.0.0)
Expand Down

0 comments on commit 0fbcf31

Please sign in to comment.