For #19938: Remove a11y workaround for tab tray new tab button.

Historically, button was introduced in a3dc565c10,
because FAB was not selectable by a11y in previous implementation of tab tray.
This commit is contained in:
mcarare 2021-06-11 16:27:02 +03:00 committed by Jonathan Almeida
parent 4de1edaa19
commit d43acbd03d
6 changed files with 11 additions and 345 deletions

View File

@ -1,100 +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.tabstray
import android.annotation.SuppressLint
import android.view.View
import android.widget.ImageButton
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import mozilla.components.lib.state.helpers.AbstractBinding
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import org.mozilla.fenix.R
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
import org.mozilla.fenix.utils.Settings
/**
* A binding for an accessible [actionButton] that is updated on the selected page.
*
* Do not show accessible new tab button when accessibility service is disabled
*
* This binding is coupled with [FloatingActionButtonBinding].
* When [FloatingActionButtonBinding] is visible this should not be visible
*/
@OptIn(ExperimentalCoroutinesApi::class)
class AccessibleNewTabButtonBinding(
private val store: TabsTrayStore,
private val settings: Settings,
private val actionButton: ImageButton,
private val browserTrayInteractor: BrowserTrayInteractor
) : AbstractBinding<TabsTrayState>(store) {
// suppressing for the intentional behaviour of this feature.
@SuppressLint("MissingSuperCall")
override fun start() {
if (!settings.accessibilityServicesEnabled) {
actionButton.visibility = View.GONE
return
}
super.start()
}
override suspend fun onState(flow: Flow<TabsTrayState>) {
flow.map { it }
.ifAnyChanged { state ->
arrayOf(
state.selectedPage,
state.syncing
)
}
.collect { state ->
setAccessibleNewTabButton(state.selectedPage, state.syncing)
}
}
private fun setAccessibleNewTabButton(selectedPage: Page, syncing: Boolean) {
when (selectedPage) {
Page.NormalTabs -> {
actionButton.apply {
visibility = View.VISIBLE
contentDescription = context.getString(R.string.add_tab)
setImageResource(R.drawable.ic_new)
setOnClickListener {
browserTrayInteractor.onFabClicked(false)
}
}
}
Page.PrivateTabs -> {
actionButton.apply {
visibility = View.VISIBLE
contentDescription = context.getString(R.string.add_private_tab)
setImageResource(R.drawable.ic_new)
setOnClickListener {
browserTrayInteractor.onFabClicked(true)
}
}
}
Page.SyncedTabs -> {
actionButton.apply {
visibility = when (syncing) {
true -> View.GONE
false -> View.VISIBLE
}
contentDescription = context.getString(R.string.tab_drawer_fab_sync)
setImageResource(R.drawable.ic_fab_sync)
setOnClickListener {
// Notify the store observers (one of which is the SyncedTabsFeature), that
// a sync was requested.
if (!syncing) {
store.dispatch(TabsTrayAction.SyncNow)
}
}
}
}
}
}
}

View File

@ -13,32 +13,17 @@ import mozilla.components.lib.state.helpers.AbstractBinding
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import org.mozilla.fenix.R
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
import org.mozilla.fenix.utils.Settings
/**
* A binding for an accessible [actionButton] that is updated on the selected page.
*
* Do not show fab when accessibility service is enabled
*
* This binding is coupled with [AccessibleNewTabButtonBinding].
* When [AccessibleNewTabButtonBinding] is visible this should not be visible
* A binding that show a FAB in tab tray used to open a new tab.
*/
@OptIn(ExperimentalCoroutinesApi::class)
class FloatingActionButtonBinding(
private val store: TabsTrayStore,
private val settings: Settings,
private val actionButton: ExtendedFloatingActionButton,
private val browserTrayInteractor: BrowserTrayInteractor
) : AbstractBinding<TabsTrayState>(store) {
override fun start() {
if (settings.accessibilityServicesEnabled) {
actionButton.hide()
return
}
super.start()
}
override suspend fun onState(flow: Flow<TabsTrayState>) {
flow.map { it }
.ifAnyChanged { state ->

View File

@ -21,8 +21,6 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.tabs.TabLayout
import kotlinx.android.synthetic.main.component_tabstray2.*
import kotlinx.android.synthetic.main.component_tabstray2.view.*
import kotlinx.android.synthetic.main.component_tabstray2.view.tab_tray_overflow
import kotlinx.android.synthetic.main.component_tabstray2.view.tab_wrapper
import kotlinx.android.synthetic.main.component_tabstray_fab.*
import kotlinx.android.synthetic.main.fragment_tab_tray_dialog.*
import kotlinx.android.synthetic.main.tabs_tray_tab_counter2.*
@ -35,6 +33,7 @@ import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
@ -44,17 +43,16 @@ import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.home.HomeScreenViewModel
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
import org.mozilla.fenix.tabstray.browser.DefaultBrowserTrayInteractor
import org.mozilla.fenix.tabstray.browser.SelectionHandleBinding
import org.mozilla.fenix.tabstray.browser.SelectionBannerBinding
import org.mozilla.fenix.tabstray.browser.SelectionBannerBinding.VisibilityModifier
import org.mozilla.fenix.tabstray.ext.showWithTheme
import org.mozilla.fenix.tabstray.browser.SelectionHandleBinding
import org.mozilla.fenix.tabstray.ext.anchorWithAction
import org.mozilla.fenix.tabstray.ext.make
import org.mozilla.fenix.tabstray.ext.message
import org.mozilla.fenix.tabstray.ext.orDefault
import org.mozilla.fenix.tabstray.ext.showWithTheme
import org.mozilla.fenix.utils.allowUndo
import kotlin.math.max
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.tabstray.ext.make
import org.mozilla.fenix.tabstray.ext.orDefault
import org.mozilla.fenix.tabstray.ext.message
@Suppress("TooManyFunctions", "LargeClass")
class TabsTrayFragment : AppCompatDialogFragment() {
@ -68,7 +66,6 @@ class TabsTrayFragment : AppCompatDialogFragment() {
private val tabLayoutMediator = ViewBoundFeatureWrapper<TabLayoutMediator>()
private val tabCounterBinding = ViewBoundFeatureWrapper<TabCounterBinding>()
private val floatingActionButtonBinding = ViewBoundFeatureWrapper<FloatingActionButtonBinding>()
private val newTabButtonBinding = ViewBoundFeatureWrapper<AccessibleNewTabButtonBinding>()
private val selectionBannerBinding = ViewBoundFeatureWrapper<SelectionBannerBinding>()
private val selectionHandleBinding = ViewBoundFeatureWrapper<SelectionHandleBinding>()
private val tabsTrayCtaBinding = ViewBoundFeatureWrapper<TabsTrayInfoBannerBinding>()
@ -215,7 +212,6 @@ class TabsTrayFragment : AppCompatDialogFragment() {
floatingActionButtonBinding.set(
feature = FloatingActionButtonBinding(
store = tabsTrayStore,
settings = requireComponents.settings,
actionButton = new_tab_button,
browserTrayInteractor = browserTrayInteractor
),
@ -223,17 +219,6 @@ class TabsTrayFragment : AppCompatDialogFragment() {
view = view
)
newTabButtonBinding.set(
feature = AccessibleNewTabButtonBinding(
store = tabsTrayStore,
settings = requireComponents.settings,
actionButton = tab_tray_new_tab,
browserTrayInteractor = browserTrayInteractor
),
owner = this,
view = view
)
selectionBannerBinding.set(
feature = SelectionBannerBinding(
context = requireContext(),

View File

@ -111,19 +111,6 @@
</com.google.android.material.tabs.TabLayout>
<ImageButton
android:id="@+id/tab_tray_new_tab"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/add_tab"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/tab_layout"
app:layout_constraintEnd_toStartOf="@id/tab_tray_overflow"
app:layout_constraintTop_toTopOf="@id/tab_layout"
app:srcCompat="@drawable/ic_new"
app:tint="@color/primary_text_normal_theme" />
<ImageButton
android:id="@+id/tab_tray_overflow"
android:layout_width="48dp"

View File

@ -1,151 +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.tabstray
import android.content.Context
import android.view.View
import android.widget.ImageButton
import androidx.appcompat.content.res.AppCompatResources
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
import mozilla.components.support.test.libstate.ext.waitUntilIdle
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
import org.mozilla.fenix.utils.Settings
class AccessibleNewTabButtonBindingTest {
@OptIn(ExperimentalCoroutinesApi::class)
@get:Rule
val coroutinesTestRule = MainCoroutineRule(TestCoroutineDispatcher())
private val settings: Settings = mockk(relaxed = true)
private val actionButton: ImageButton = mockk(relaxed = true)
private val browserTrayInteractor: BrowserTrayInteractor = mockk(relaxed = true)
private val context: Context = mockk(relaxed = true)
@Before
fun setup() {
mockkStatic(AppCompatResources::class)
every { AppCompatResources.getDrawable(any(), any()) } returns mockk(relaxed = true)
every { actionButton.context } returns context
}
@After
fun teardown() {
unmockkStatic(AppCompatResources::class)
}
@Test
fun `WHEN tab selected page is normal tab THEN new tab button is visible`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
val newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
every { settings.accessibilityServicesEnabled } returns true
newTabButtonBinding.start()
verify(exactly = 1) { actionButton.visibility = View.VISIBLE }
}
@Test
fun `WHEN tab selected page is private tab THEN new tab button is visible`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs))
val newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
every { settings.accessibilityServicesEnabled } returns true
newTabButtonBinding.start()
verify(exactly = 1) { actionButton.visibility = View.VISIBLE }
}
@Test
fun `WHEN tab selected page is sync tab THEN new tab button is visible`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs))
val newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
every { settings.accessibilityServicesEnabled } returns true
newTabButtonBinding.start()
verify(exactly = 1) { actionButton.visibility = View.VISIBLE }
}
@Test
fun `WHEN accessibility is disabled THEN new tab button is not visible`() {
var tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
var newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
every { settings.accessibilityServicesEnabled } returns false
newTabButtonBinding.start()
verify(exactly = 1) { actionButton.visibility = View.GONE }
tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs))
newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
newTabButtonBinding.start()
verify(exactly = 2) { actionButton.visibility = View.GONE }
tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs))
newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
newTabButtonBinding.start()
verify(exactly = 3) { actionButton.visibility = View.GONE }
}
@Test
fun `WHEN selected page is updated THEN button is updated`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
val newTabButtonBinding = AccessibleNewTabButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
every { settings.accessibilityServicesEnabled } returns true
newTabButtonBinding.start()
verify(exactly = 1) { actionButton.setImageResource(R.drawable.ic_new) }
verify(exactly = 1) { actionButton.contentDescription = any() }
tabsTrayStore.dispatch(TabsTrayAction.PageSelected(Page.positionToPage(Page.PrivateTabs.ordinal)))
tabsTrayStore.waitUntilIdle()
verify(exactly = 2) { actionButton.setImageResource(R.drawable.ic_new) }
verify(exactly = 2) { actionButton.contentDescription = any() }
tabsTrayStore.dispatch(TabsTrayAction.PageSelected(Page.positionToPage(Page.SyncedTabs.ordinal)))
tabsTrayStore.waitUntilIdle()
verify(exactly = 1) { actionButton.setImageResource(R.drawable.ic_fab_sync) }
verify(exactly = 3) { actionButton.contentDescription = any() }
tabsTrayStore.dispatch(TabsTrayAction.SyncNow)
tabsTrayStore.waitUntilIdle()
verify(exactly = 1) { actionButton.visibility = View.GONE }
}
}

View File

@ -21,7 +21,6 @@ import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
import org.mozilla.fenix.utils.Settings
class FloatingActionButtonBindingTest {
@ -29,7 +28,6 @@ class FloatingActionButtonBindingTest {
@get:Rule
val coroutinesTestRule = MainCoroutineRule(TestCoroutineDispatcher())
private val settings: Settings = mockk(relaxed = true)
private val actionButton: ExtendedFloatingActionButton = mockk(relaxed = true)
private val browserTrayInteractor: BrowserTrayInteractor = mockk(relaxed = true)
@ -48,9 +46,8 @@ class FloatingActionButtonBindingTest {
fun `WHEN tab selected page is normal tab THEN shrink and show is called`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
val fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor
tabsTrayStore, actionButton, browserTrayInteractor
)
every { settings.accessibilityServicesEnabled } returns false
fabBinding.start()
@ -64,9 +61,8 @@ class FloatingActionButtonBindingTest {
fun `WHEN tab selected page is private tab THEN extend and show is called`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs))
val fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor
tabsTrayStore, actionButton, browserTrayInteractor
)
every { settings.accessibilityServicesEnabled } returns false
fabBinding.start()
@ -80,9 +76,8 @@ class FloatingActionButtonBindingTest {
fun `WHEN tab selected page is sync tab THEN extend and show is called`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs))
val fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor
tabsTrayStore, actionButton, browserTrayInteractor
)
every { settings.accessibilityServicesEnabled } returns false
fabBinding.start()
@ -92,47 +87,12 @@ class FloatingActionButtonBindingTest {
verify(exactly = 0) { actionButton.hide() }
}
@Test
fun `WHEN accessibility is enabled THEN show is not called`() {
var tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
var fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
every { settings.accessibilityServicesEnabled } returns true
fabBinding.start()
verify(exactly = 0) { actionButton.show() }
verify(exactly = 1) { actionButton.hide() }
tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs))
fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
fabBinding.start()
verify(exactly = 0) { actionButton.show() }
verify(exactly = 2) { actionButton.hide() }
tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs))
fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor
)
fabBinding.start()
verify(exactly = 0) { actionButton.show() }
verify(exactly = 3) { actionButton.hide() }
}
@Test
fun `WHEN selected page is updated THEN button is updated`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
val fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor
tabsTrayStore, actionButton, browserTrayInteractor
)
every { settings.accessibilityServicesEnabled } returns false
fabBinding.start()