2
0
mirror of https://github.com/fork-maintainers/iceraven-browser synced 2024-11-19 09:25:34 +00:00

[fenix] For https://github.com/mozilla-mobile/fenix/issues/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 2dafd2dcea
commit 7675e4e8c0
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 mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor 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. * A binding that show a FAB in tab tray used to open a new tab.
*
* Do not show fab when accessibility service is enabled
*
* This binding is coupled with [AccessibleNewTabButtonBinding].
* When [AccessibleNewTabButtonBinding] is visible this should not be visible
*/ */
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class FloatingActionButtonBinding( class FloatingActionButtonBinding(
private val store: TabsTrayStore, private val store: TabsTrayStore,
private val settings: Settings,
private val actionButton: ExtendedFloatingActionButton, private val actionButton: ExtendedFloatingActionButton,
private val browserTrayInteractor: BrowserTrayInteractor private val browserTrayInteractor: BrowserTrayInteractor
) : AbstractBinding<TabsTrayState>(store) { ) : AbstractBinding<TabsTrayState>(store) {
override fun start() {
if (settings.accessibilityServicesEnabled) {
actionButton.hide()
return
}
super.start()
}
override suspend fun onState(flow: Flow<TabsTrayState>) { override suspend fun onState(flow: Flow<TabsTrayState>) {
flow.map { it } flow.map { it }
.ifAnyChanged { state -> .ifAnyChanged { state ->

View File

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

View File

@ -111,19 +111,6 @@
</com.google.android.material.tabs.TabLayout> </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 <ImageButton
android:id="@+id/tab_tray_overflow" android:id="@+id/tab_tray_overflow"
android:layout_width="48dp" 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.junit.Test
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor import org.mozilla.fenix.tabstray.browser.BrowserTrayInteractor
import org.mozilla.fenix.utils.Settings
class FloatingActionButtonBindingTest { class FloatingActionButtonBindingTest {
@ -29,7 +28,6 @@ class FloatingActionButtonBindingTest {
@get:Rule @get:Rule
val coroutinesTestRule = MainCoroutineRule(TestCoroutineDispatcher()) val coroutinesTestRule = MainCoroutineRule(TestCoroutineDispatcher())
private val settings: Settings = mockk(relaxed = true)
private val actionButton: ExtendedFloatingActionButton = mockk(relaxed = true) private val actionButton: ExtendedFloatingActionButton = mockk(relaxed = true)
private val browserTrayInteractor: BrowserTrayInteractor = 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`() { fun `WHEN tab selected page is normal tab THEN shrink and show is called`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
val fabBinding = FloatingActionButtonBinding( val fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor tabsTrayStore, actionButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns false
fabBinding.start() fabBinding.start()
@ -64,9 +61,8 @@ class FloatingActionButtonBindingTest {
fun `WHEN tab selected page is private tab THEN extend and show is called`() { fun `WHEN tab selected page is private tab THEN extend and show is called`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.PrivateTabs))
val fabBinding = FloatingActionButtonBinding( val fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor tabsTrayStore, actionButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns false
fabBinding.start() fabBinding.start()
@ -80,9 +76,8 @@ class FloatingActionButtonBindingTest {
fun `WHEN tab selected page is sync tab THEN extend and show is called`() { fun `WHEN tab selected page is sync tab THEN extend and show is called`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.SyncedTabs))
val fabBinding = FloatingActionButtonBinding( val fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor tabsTrayStore, actionButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns false
fabBinding.start() fabBinding.start()
@ -92,47 +87,12 @@ class FloatingActionButtonBindingTest {
verify(exactly = 0) { actionButton.hide() } 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 @Test
fun `WHEN selected page is updated THEN button is updated`() { fun `WHEN selected page is updated THEN button is updated`() {
val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs)) val tabsTrayStore = TabsTrayStore(TabsTrayState(selectedPage = Page.NormalTabs))
val fabBinding = FloatingActionButtonBinding( val fabBinding = FloatingActionButtonBinding(
tabsTrayStore, settings, actionButton, browserTrayInteractor tabsTrayStore, actionButton, browserTrayInteractor
) )
every { settings.accessibilityServicesEnabled } returns false
fabBinding.start() fabBinding.start()