Commit f93120cd authored by Jonathan Almeida's avatar Jonathan Almeida Committed by Jonathan Almeida
Browse files

Closes #2816: Add TabTouchCallback for swipe support

parent f20be143
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@ class BrowserTabsTray @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0,
    private val tabsAdapter: TabsAdapter = TabsAdapter()
    val tabsAdapter: TabsAdapter = TabsAdapter()
) : RecyclerView(context, attrs, defStyleAttr),
    TabsTray by tabsAdapter {

+54 −0
Original line number Diff line number Diff line
/*
 * 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.browser.tabstray

import android.graphics.Canvas
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.helper.ItemTouchHelper
import mozilla.components.concept.tabstray.TabsTray
import mozilla.components.support.base.observer.Observable

/**
 * An [ItemTouchHelper.Callback] for support gestures on tabs in the tray.
 */
open class TabTouchCallback(
    private val observable: Observable<TabsTray.Observer>
) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {

    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
        with(viewHolder as TabViewHolder) {
            session?.let { observable.notifyObservers { onTabClosed(it) } }
        }
    }

    override fun onChildDraw(
        c: Canvas,
        recyclerView: RecyclerView,
        viewHolder: RecyclerView.ViewHolder,
        dX: Float,
        dY: Float,
        actionState: Int,
        isCurrentlyActive: Boolean
    ) {
        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
            // Alpha on an itemView being swiped should decrease to a min over a distance equal to
            // the width of the item being swiped.
            viewHolder.itemView.alpha = alphaForItemSwipe(dX, viewHolder.itemView.width)
        }

        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
    }

    /**
     * Sets the alpha value for a swipe gesture. This is useful for inherited classes to provide their own values.
     */
    open fun alphaForItemSwipe(dX: Float, distanceToAlphaMin: Int): Float {
        return 1f
    }

    override fun onMove(p0: RecyclerView, p1: RecyclerView.ViewHolder, p2: RecyclerView.ViewHolder): Boolean = false
}
+1 −1
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@ class TabViewHolder(
    private val closeView: AppCompatImageButton = itemView.findViewById(R.id.mozac_browser_tabstray_close)
    private val thumbnailView: TabThumbnailView = itemView.findViewById(R.id.mozac_browser_tabstray_thumbnail)

    private var session: Session? = null
    internal var session: Session? = null

    /**
     * Displays the data of the given session and notifies the given observable about events.
+82 −0
Original line number Diff line number Diff line
/*
 * 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.browser.tabstray

import android.content.Context
import android.support.v7.widget.helper.ItemTouchHelper
import android.view.LayoutInflater
import androidx.test.core.app.ApplicationProvider
import mozilla.components.concept.tabstray.TabsTray
import mozilla.components.support.base.observer.Observable
import mozilla.components.support.base.observer.ObserverRegistry
import mozilla.components.support.test.any
import mozilla.components.support.test.mock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class TabTouchCallbackTest {

    private val context: Context
        get() = ApplicationProvider.getApplicationContext()

    @Test
    fun `onSwiped notifies observers`() {
        val observer: TabsTray.Observer = mock()
        val observable: Observable<TabsTray.Observer> = ObserverRegistry()
        observable.register(observer)

        val touchCallback = TabTouchCallback(observable)
        val viewHolder: TabViewHolder = mock()

        touchCallback.onSwiped(viewHolder, 0)

        verify(observer, never()).onTabClosed(any())

        // With a session available.
        `when`(viewHolder.session).thenReturn(mock())

        touchCallback.onSwiped(viewHolder, 0)

        verify(observer).onTabClosed(any())
    }

    @Test
    fun `onChildDraw alters alpha of ViewHolder on swipe gesture`() {
        val view = LayoutInflater.from(context).inflate(R.layout.mozac_browser_tabstray_item, null)
        val holder = TabViewHolder(view, TabViewHolderTest.mockTabsTrayWithStyles())
        val callback = TabTouchCallback(mock())

        holder.itemView.alpha = 0f

        callback.onChildDraw(mock(), mock(), holder, 0f, 0f, ItemTouchHelper.ACTION_STATE_DRAG, true)

        assertEquals(0f, holder.itemView.alpha)

        callback.onChildDraw(mock(), mock(), holder, 0f, 0f, ItemTouchHelper.ACTION_STATE_SWIPE, true)

        assertEquals(1f, holder.itemView.alpha)
    }

    @Test
    fun `alpha default is full`() {
        val touchCallback = TabTouchCallback(mock())
        assertEquals(1f, touchCallback.alphaForItemSwipe(0f, 0))
    }

    @Test
    fun `onMove is not implemented`() {
        val touchCallback = TabTouchCallback(mock())
        assertFalse(touchCallback.onMove(mock(), mock(), mock()))
    }
}
+73 −5
Original line number Diff line number Diff line
@@ -16,10 +16,14 @@ import mozilla.components.concept.tabstray.TabsTray
import mozilla.components.support.test.mock
import mozilla.components.support.base.observer.ObserverRegistry
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.robolectric.RobolectricTestRunner

@@ -115,6 +119,68 @@ class TabViewHolderTest {
        verify(observer).onTabClosed(session)
    }

    @Test
    fun `url from session is displayed by default`() {
        val observer: TabsTray.Observer = mock()
        val registry = ObserverRegistry<TabsTray.Observer>().also {
            it.register(observer)
        }

        val view = LayoutInflater.from(context).inflate(R.layout.mozac_browser_tabstray_item, null)
        val holder = TabViewHolder(view, mockTabsTrayWithStyles())

        val session = Session("https://www.mozilla.org")
        val titleView = holder.itemView.findViewById<TextView>(R.id.mozac_browser_tabstray_url)

        holder.bind(session, isSelected = true, observable = registry)

        assertEquals(session.url, titleView.text)
    }

    @Test
    fun `title from session is displayed if available`() {
        val observer: TabsTray.Observer = mock()
        val registry = ObserverRegistry<TabsTray.Observer>().also {
            it.register(observer)
        }

        val view = LayoutInflater.from(context).inflate(R.layout.mozac_browser_tabstray_item, null)
        val holder = TabViewHolder(view, mockTabsTrayWithStyles())

        val session = Session("https://www.mozilla.org")
        val titleView = holder.itemView.findViewById<TextView>(R.id.mozac_browser_tabstray_url)

        session.title = "Mozilla Firefox"

        holder.bind(session, isSelected = true, observable = registry)

        assertEquals("Mozilla Firefox", titleView.text)
    }

    @Test
    fun `session unregisters on unbind`() {
        val observer: TabsTray.Observer = mock()
        val registry = ObserverRegistry<TabsTray.Observer>().also {
            it.register(observer)
        }

        val view = LayoutInflater.from(context).inflate(R.layout.mozac_browser_tabstray_item, null)
        val holder = TabViewHolder(view, mockTabsTrayWithStyles())
        val session = spy(Session("https://www.mozilla.org"))

        holder.unbind()

        assertNull(holder.session)
        verify(session, never()).unregister(holder)

        holder.bind(session, isSelected = true, observable = registry)

        holder.unbind()

        assertNotNull(holder.session)
        verify(session).unregister(holder)
    }

    @Test
    fun `thumbnail from session is assigned to thumbnail image view`() {
        val view = LayoutInflater.from(context).inflate(R.layout.mozac_browser_tabstray_item, null)
@@ -130,7 +196,8 @@ class TabViewHolderTest {
        assertTrue(thumbnailView.drawable != null)
    }

    private fun mockTabsTrayWithStyles(): BrowserTabsTray {
    companion object {
        fun mockTabsTrayWithStyles(): BrowserTabsTray {
            val styles: TabsTrayStyling = mock()

            val tabsTray: BrowserTabsTray = mock()
@@ -139,3 +206,4 @@ class TabViewHolderTest {
            return tabsTray
        }
    }
}
Loading