diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.kt b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.kt index b2eb9971a4ea..d76b891bc3eb 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPickerFloatingActionMenu.kt @@ -21,7 +21,10 @@ import android.content.res.ColorStateList import android.view.KeyEvent import android.view.MotionEvent import android.view.View +import android.view.accessibility.AccessibilityEvent import android.widget.LinearLayout +import androidx.core.view.ViewCompat +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import com.google.android.material.color.MaterialColors import com.ichi2.anki.databinding.ActivityHomescreenBinding import com.ichi2.anki.databinding.FloatingAddButtonBinding @@ -65,6 +68,7 @@ class DeckPickerFloatingActionMenu( linearLayout.alpha = 0.5f studyOptionsFrame?.let { it.alpha = 0.5f } isFABOpen = true + setOptionFocusable(true) if (deckPicker.animationEnabled()) { // Show with animation binding.addSharedLayout.visibility = View.VISIBLE @@ -131,13 +135,21 @@ class DeckPickerFloatingActionMenu( setImageResource(addNoteIcon) } } + // Post the focus request to ensure the view is ready to receive it. + binding.addSharedButton.post { + binding.addNoteLabel.requestFocus() + binding.addNoteLabel.sendAccessibilityEvent( + AccessibilityEvent.TYPE_VIEW_FOCUSED, + ) + } + updateAccessibilityState() } /** * This function takes a parameter which decides if we want to apply the rise and shrink animation * for FAB or not. * - * Case 1: When the FAB is already opened and we close it by pressing the back button then we need to show + * Case 1: When the FAB is already opened, and we close it by pressing the back button then we need to show * the rise and shrink animation and get back to the FAB with `+` icon. * * Case 2: When the user opens the side navigation drawer (without touching the FAB). In that case we don't @@ -149,6 +161,7 @@ class DeckPickerFloatingActionMenu( linearLayout.alpha = 1f studyOptionsFrame?.let { it.alpha = 1f } isFABOpen = false + setOptionFocusable(false) binding.fabBGLayout.visibility = View.GONE binding.addNoteLabel.visibility = View.GONE if (deckPicker.animationEnabled()) { @@ -238,6 +251,7 @@ class DeckPickerFloatingActionMenu( linearLayout.alpha = 1f studyOptionsFrame?.let { it.alpha = 1f } isFABOpen = false + setOptionFocusable(false) binding.fabBGLayout.visibility = View.GONE binding.addNoteLabel.visibility = View.GONE if (deckPicker.animationEnabled()) { @@ -314,6 +328,58 @@ class DeckPickerFloatingActionMenu( binding.fabMain.setImageResource(addWhiteIcon) } } + updateAccessibilityState() + } + + private fun updateAccessibilityState() { + if (isFABOpen) { + // When the menu is OPEN, the action is to close it. + binding.fabMain.contentDescription = context.getString(R.string.menu_add_note) + ViewCompat.replaceAccessibilityAction( + binding.fabMain, + AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK, + context.getString(R.string.menu_add_note), + ) { _, _ -> + addNote() + true + } + } else { + // When the menu is CLOSED, the action is to open it. + binding.fabMain.contentDescription = context.getString(R.string.toggle_fab_menu) + ViewCompat.replaceAccessibilityAction( + binding.fabMain, + AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK, + context.getString(R.string.toggle_fab_menu), + ) { _, _ -> + showFloatingActionMenu() + true + } + } + } + + private fun setOptionFocusable(isFocusable: Boolean) { + val importance = + if (isFocusable) { + View.IMPORTANT_FOR_ACCESSIBILITY_YES + } else { + View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS + } + + val views = + listOf( + binding.addDeckButton, + binding.addDeckLabel, + binding.addFilteredDeckButton, + binding.addFilteredDeckLabel, + binding.addSharedButton, + binding.addSharedLabel, + binding.addNoteLabel, + ) + + views.forEach { + it.isFocusable = isFocusable + it.importantForAccessibility = importance + } } fun showFloatingActionButton() { @@ -321,6 +387,7 @@ class DeckPickerFloatingActionMenu( Timber.i("DeckPicker:: showFloatingActionButton()") binding.fabMain.visibility = View.VISIBLE } + updateAccessibilityState() } fun hideFloatingActionButton() { @@ -328,6 +395,7 @@ class DeckPickerFloatingActionMenu( Timber.i("DeckPicker:: hideFloatingActionButton()") binding.fabMain.visibility = View.GONE } + updateAccessibilityState() } private fun createActivationKeyListener( @@ -346,6 +414,7 @@ class DeckPickerFloatingActionMenu( } init { + setOptionFocusable(false) binding.fabMain.setOnTouchListener( object : DoubleTapListener(context) { override fun onDoubleTap(e: MotionEvent?) { @@ -358,6 +427,7 @@ class DeckPickerFloatingActionMenu( if (!isFABOpen) { showFloatingActionMenu() } else { + closeFloatingActionMenu(true) addNote() } } diff --git a/AnkiDroid/src/main/res/values/01-core.xml b/AnkiDroid/src/main/res/values/01-core.xml index 3ddeba5ebf5d..cddc34e66d3b 100644 --- a/AnkiDroid/src/main/res/values/01-core.xml +++ b/AnkiDroid/src/main/res/values/01-core.xml @@ -43,6 +43,9 @@ Collection is empty Start adding cards\nusing the + icon. + + Toggle FAB menu + %d minute left