<div dir="ltr">just want to check in on any changes i need to make <br><br></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Sun, 18 Jan 2026 at 15:43, aryan <<a href="mailto:aryanchoudharyk2008@gmail.com">aryanchoudharyk2008@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Changes in v2:<br>
- Moved AccessibilityDelegate to onBindViewHolder to fix actions disappearing on scroll.<br>
<br>
Hello VideoLAN Android Team,<br>
I am submitting a patch to improve the TalkBack experience in the media browser and player lists.<br>
The Problem: Currently, list items require excessive swiping to access common actions (like "More options"), and the interface does not expose quick actions like "Remove from favorites" or "Seek" directly to accessibility services.<br>
The Fix: This patch implements AccessibilityDelegateCompat to:<br>
1. <br>
Add Custom Accessibility Actions (Seek, Favorite, Play Next, etc.) directly to the list items.<br>
2. <br>
Hide the redundant "More" button from TalkBack focus (IMPORTANT_FOR_ACCESSIBILITY_NO) to reduce clutter.<br>
3. <br>
Dynamically update action labels (e.g., swapping "Add to favorites" with "Remove from favorites" based on state).<br>
I am submitting this via the mailing list as I am unable to create an account on <a href="http://code.videolan.org" rel="noreferrer" target="_blank">code.videolan.org</a> at the moment.<br>
Best regards, Aryan<br>
<br>
---<br>
.../resources/src/main/res/values/ids.xml | 24 +++<br>
.../vlc/gui/audio/AudioBrowserAdapter.kt | 127 +++++++++++++++<br>
.../vlc/gui/browser/BaseBrowserAdapter.kt | 132 ++++++++++++++++<br>
.../vlc/gui/video/VideoGridFragment.kt | 4 +<br>
.../vlc/gui/video/VideoListAdapter.kt | 146 +++++++++++++++++-<br>
5 files changed, 432 insertions(+), 1 deletion(-)<br>
<br>
diff --git a/application/resources/src/main/res/values/ids.xml b/application/resources/src/main/res/values/ids.xml<br>
index 5ff0ec18e..59ad7b7ab 100644<br>
--- a/application/resources/src/main/res/values/ids.xml<br>
+++ b/application/resources/src/main/res/values/ids.xml<br>
@@ -25,4 +25,28 @@<br>
<resources><br>
<item type="id" name="sort" /><br>
<item type="id" name="sort_desc" /><br>
+ <item type="id" name="accessibility_action_append" /><br>
+ <item type="id" name="accessibility_action_play_next" /><br>
+ <item type="id" name="accessibility_action_delete" /><br>
+ <item type="id" name="accessibility_action_add_to_playlist" /><br>
+ <item type="id" name="accessibility_action_info" /><br>
+ <item type="id" name="accessibility_action_play_all" /><br>
+ <item type="id" name="accessibility_action_share" /><br>
+ <item type="id" name="accessibility_action_set_ringtone" /><br>
+ <item type="id" name="accessibility_action_download_subtitles" /><br>
+ <item type="id" name="accessibility_action_play_as_audio" /><br>
+ <item type="id" name="accessibility_action_add_favorite" /><br>
+ <item type="id" name="accessibility_action_remove_favorite" /><br>
+ <item type="id" name="accessibility_action_mark_played" /><br>
+ <item type="id" name="accessibility_action_mark_unplayed" /><br>
+ <item type="id" name="accessibility_action_add_shortcut" /><br>
+ <item type="id" name="accessibility_action_go_to_folder" /><br>
+ <item type="id" name="accessibility_action_add_group" /><br>
+ <item type="id" name="accessibility_action_group_similar" /><br>
+ <item type="id" name="accessibility_action_rename" /><br>
+ <item type="id" name="accessibility_action_play_from_start" /><br>
+ <item type="id" name="accessibility_action_go_to_album" /><br>
+ <item type="id" name="accessibility_action_go_to_artist" /><br>
+ <item type="id" name="accessibility_action_ban_folder" /><br>
+ <item type="id" name="accessibility_action_open_context_menu" /><br>
</resources><br>
\ No newline at end of file<br>
diff --git a/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserAdapter.kt b/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserAdapter.kt<br>
index a8be20509..33572698e 100644<br>
--- a/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserAdapter.kt<br>
+++ b/application/vlc-android/src/org/videolan/vlc/gui/audio/AudioBrowserAdapter.kt<br>
@@ -41,9 +41,11 @@ import androidx.paging.PagedListAdapter<br>
import androidx.recyclerview.widget.DiffUtil<br>
import androidx.recyclerview.widget.RecyclerView<br>
import org.videolan.libvlc.util.AndroidUtil<br>
+import org.videolan.medialibrary.interfaces.media.Album<br>
import org.videolan.medialibrary.interfaces.media.Artist<br>
import org.videolan.medialibrary.interfaces.media.Genre<br>
import org.videolan.medialibrary.interfaces.media.MediaWrapper<br>
+import org.videolan.medialibrary.interfaces.media.Playlist<br>
import org.videolan.medialibrary.media.MediaLibraryItem<br>
import org.videolan.medialibrary.media.MediaLibraryItem.FLAG_SELECTED<br>
import org.videolan.resources.AppContextProvider<br>
@@ -73,6 +75,22 @@ import org.videolan.vlc.util.isOTG<br>
import org.videolan.vlc.util.isSD<br>
import org.videolan.vlc.util.isSchemeSMB<br>
import org.videolan.vlc.viewmodels.PlaylistModel<br>
+import android.os.Bundle<br>
+import androidx.core.view.AccessibilityDelegateCompat<br>
+import androidx.core.view.ViewCompat<br>
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat<br>
+import <a href="http://androidx.appcompat.app" target="_blank">androidx.appcompat.app</a>.AppCompatActivity<br>
+import <a href="http://androidx.fragment.app" target="_blank">androidx.fragment.app</a>.FragmentActivity<br>
+import org.videolan.vlc.media.MediaUtils<br>
+import org.videolan.vlc.gui.helpers.UiTools<br>
+import org.videolan.vlc.gui.helpers.UiTools.addToPlaylist<br>
+import org.videolan.vlc.gui.helpers.UiTools.showMediaInfo<br>
+import org.videolan.vlc.util.share<br>
+import androidx.lifecycle.lifecycleScope<br>
+import org.videolan.vlc.util.ContextOption<br>
+import org.videolan.vlc.gui.dialogs.CtxActionReceiver<br>
+import org.videolan.vlc.media.getAll<br>
+import org.videolan.tools.retrieveParent<br>
<br>
private const val SHOW_IN_LIST = -1<br>
<br>
@@ -172,6 +190,7 @@ open class AudioBrowserAdapter @JvmOverloads constructor(<br>
if (position >= itemCount) return<br>
val item = getItem(position)<br>
holder.setItem(item)<br>
+ holder.updateAccessibilityDelegate(item)<br>
if (item is Artist) item.description = holder.binding.root.context.resources.getQuantityString(R.plurals.albums_quantity, item.albumsCount, item.albumsCount)<br>
if (item is Genre) item.description = holder.binding.root.context.resources.getQuantityString(R.plurals.track_quantity, item.tracksCount, item.tracksCount)<br>
val isSelected = multiSelectHelper.isSelected(position)<br>
@@ -314,6 +333,7 @@ open class AudioBrowserAdapter @JvmOverloads constructor(<br>
}<br>
}<br>
binding.imageWidth = listImageWidth<br>
+ binding.itemMore.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO<br>
}<br>
<br>
override fun selectView(selected: Boolean) {<br>
@@ -355,6 +375,7 @@ open class AudioBrowserAdapter @JvmOverloads constructor(<br>
}<br>
binding.imageWidth = cardSize<br>
binding.container.layoutParams.width = cardSize<br>
+ binding.itemMore.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO<br>
<br>
}<br>
<br>
@@ -384,6 +405,112 @@ open class AudioBrowserAdapter @JvmOverloads constructor(<br>
@TargetApi(Build.VERSION_CODES.M)<br>
abstract inner class AbstractMediaItemViewHolder<T : ViewDataBinding>(binding: T) : SelectorViewHolder<T>(binding), MarqueeViewHolder {<br>
<br>
+ fun updateAccessibilityDelegate(item: MediaLibraryItem?) {<br>
+ if (item == null) {<br>
+ ViewCompat.setAccessibilityDelegate(itemView, null)<br>
+ return<br>
+ }<br>
+ ViewCompat.setAccessibilityDelegate(itemView, object : AccessibilityDelegateCompat() {<br>
+ override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {<br>
+ super.onInitializeAccessibilityNodeInfo(host, info)<br>
+ val context = itemView.context<br>
+<br>
+ if (item.isFavorite) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_remove_favorite, context.getString(R.string.favorites_remove)))<br>
+ } else {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_add_favorite, context.getString(R.string.favorites_add)))<br>
+ }<br>
+<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_append, context.getString(R.string.append)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_play_next, context.getString(R.string.insert_next)))<br>
+ <br>
+ if (item is MediaWrapper || item is Playlist) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_add_shortcut, context.getString(R.string.create_shortcut)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_rename, context.getString(R.string.rename)))<br>
+ }<br>
+<br>
+ if (item is MediaWrapper) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_play_all, context.getString(R.string.play_all)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_info, context.getString(<a href="http://R.string.info" rel="noreferrer" target="_blank">R.string.info</a>)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_share, context.getString(R.string.share)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_add_to_playlist, context.getString(R.string.add_to_playlist)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_delete, context.getString(R.string.delete)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_set_ringtone, context.getString(R.string.set_song)))<br>
+ if (item.uri.retrieveParent() != null) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_go_to_folder, context.getString(R.string.go_to_folder)))<br>
+ }<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_go_to_album, context.getString(R.string.go_to_album)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_go_to_artist, context.getString(R.string.go_to_artist)))<br>
+ } else if (item is Album || item is Artist || item is Genre || item is Playlist) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_play_all, context.getString(R.string.play_all)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_add_to_playlist, context.getString(R.string.add_to_playlist)))<br>
+ if (item !is Genre) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_delete, context.getString(R.string.delete)))<br>
+ }<br>
+ if (item is Album) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_go_to_artist, context.getString(R.string.go_to_artist)))<br>
+ }<br>
+ }<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_open_context_menu, context.getString(R.string.more_actions)))<br>
+ }<br>
+<br>
+ override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {<br>
+ val option = when (action) {<br>
+ R.id.accessibility_action_append -> ContextOption.CTX_APPEND<br>
+ R.id.accessibility_action_play_next -> ContextOption.CTX_PLAY_NEXT<br>
+ R.id.accessibility_action_info -> ContextOption.CTX_INFORMATION<br>
+ R.id.accessibility_action_share -> ContextOption.CTX_SHARE<br>
+ R.id.accessibility_action_add_to_playlist -> ContextOption.CTX_ADD_TO_PLAYLIST<br>
+ R.id.accessibility_action_delete -> ContextOption.CTX_DELETE<br>
+ R.id.accessibility_action_set_ringtone -> ContextOption.CTX_SET_RINGTONE<br>
+ R.id.accessibility_action_play_all -> ContextOption.CTX_PLAY_ALL<br>
+ R.id.accessibility_action_add_favorite -> ContextOption.CTX_FAV_ADD<br>
+ R.id.accessibility_action_remove_favorite -> ContextOption.CTX_FAV_REMOVE<br>
+ R.id.accessibility_action_add_shortcut -> ContextOption.CTX_ADD_SHORTCUT<br>
+ R.id.accessibility_action_go_to_folder -> ContextOption.CTX_GO_TO_FOLDER<br>
+ R.id.accessibility_action_rename -> ContextOption.CTX_RENAME<br>
+ R.id.accessibility_action_go_to_album -> ContextOption.CTX_GO_TO_ALBUM<br>
+ R.id.accessibility_action_go_to_artist -> ContextOption.CTX_GO_TO_ARTIST<br>
+ R.id.accessibility_action_open_context_menu -> {<br>
+ onMoreClick(host)<br>
+ return true<br>
+ }<br>
+ else -> null<br>
+ }<br>
+<br>
+ if (option != null && eventsHandler is CtxActionReceiver) {<br>
+ val position = layoutPosition<br>
+ if (isPositionValid(position)) {<br>
+ eventsHandler.onCtxAction(position, option)<br>
+ }<br>
+ return true<br>
+ }<br>
+<br>
+ return super.performAccessibilityAction(host, action, args)<br>
+ }<br>
+ })<br>
+ }<br>
+<br>
val canBeReordered: Boolean<br>
get() = reorderable && !stopReorder<br>
<br>
diff --git a/application/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserAdapter.kt b/application/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserAdapter.kt<br>
index 96e4803e1..33ae5aa35 100644<br>
--- a/application/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserAdapter.kt<br>
+++ b/application/vlc-android/src/org/videolan/vlc/gui/browser/BaseBrowserAdapter.kt<br>
@@ -55,6 +55,21 @@ import org.videolan.vlc.gui.view.MiniVisualizer<br>
import org.videolan.vlc.util.LifecycleAwareScheduler<br>
import org.videolan.vlc.util.getDescriptionSpan<br>
import org.videolan.vlc.viewmodels.PlaylistModel<br>
+import android.os.Bundle<br>
+import androidx.core.view.AccessibilityDelegateCompat<br>
+import androidx.core.view.ViewCompat<br>
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat<br>
+import <a href="http://androidx.appcompat.app" target="_blank">androidx.appcompat.app</a>.AppCompatActivity<br>
+import <a href="http://androidx.fragment.app" target="_blank">androidx.fragment.app</a>.FragmentActivity<br>
+import org.videolan.vlc.media.MediaUtils<br>
+import org.videolan.vlc.gui.helpers.UiTools<br>
+import org.videolan.vlc.gui.helpers.UiTools.addToPlaylist<br>
+import org.videolan.vlc.gui.helpers.UiTools.showMediaInfo<br>
+import org.videolan.vlc.util.share<br>
+import androidx.lifecycle.lifecycleScope<br>
+import org.videolan.vlc.util.ContextOption<br>
+import org.videolan.vlc.gui.dialogs.CtxActionReceiver<br>
+import org.videolan.tools.retrieveParent<br>
<br>
const val UPDATE_PROGRESS = "update_progress"<br>
open class BaseBrowserAdapter(val browserContainer: BrowserContainer<MediaLibraryItem>, var sort:Int = Medialibrary.SORT_FILENAME, var asc:Boolean = true, val forMain:Boolean = true) : DiffUtilAdapter<MediaLibraryItem, BaseBrowserAdapter.ViewHolder<ViewDataBinding>>(), MultiSelectAdapter<MediaLibraryItem>, FastScroller.SeparatedAdapter {<br>
@@ -155,6 +170,9 @@ open class BaseBrowserAdapter(val browserContainer: BrowserContainer<MediaLibrar<br>
}<br>
<br>
override fun onBindViewHolder(holder: ViewHolder<ViewDataBinding>, position: Int) {<br>
+ if (holder is MediaViewHolder) {<br>
+ holder.updateAccessibilityDelegate(dataset[position])<br>
+ }<br>
val viewType = getItemViewType(position)<br>
if (viewType == TYPE_MEDIA) {<br>
onBindMediaViewHolder(holder as MediaViewHolder, position)<br>
@@ -258,6 +276,119 @@ open class BaseBrowserAdapter(val browserContainer: BrowserContainer<MediaLibrar<br>
override val titleView: TextView = bindingContainer.title<br>
var job : Job? = null<br>
<br>
+ fun updateAccessibilityDelegate(item: MediaLibraryItem?) {<br>
+ if (item == null) {<br>
+ ViewCompat.setAccessibilityDelegate(itemView, null)<br>
+ return<br>
+ }<br>
+ ViewCompat.setAccessibilityDelegate(itemView, object : AccessibilityDelegateCompat() {<br>
+ override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {<br>
+ super.onInitializeAccessibilityNodeInfo(host, info)<br>
+ val context = itemView.context<br>
+<br>
+ if (item.isFavorite) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_remove_favorite, context.getString(R.string.favorites_remove)))<br>
+ } else {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_add_favorite, context.getString(R.string.favorites_add)))<br>
+ }<br>
+<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_append, context.getString(R.string.append)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_play_next, context.getString(R.string.insert_next)))<br>
+ <br>
+ if (item is MediaWrapper) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_add_shortcut, context.getString(R.string.create_shortcut)))<br>
+ if (item.uri.retrieveParent() != null) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_go_to_folder, context.getString(R.string.go_to_folder)))<br>
+ }<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_rename, context.getString(R.string.rename)))<br>
+ }<br>
+<br>
+ if (item is MediaWrapper) {<br>
+ if (item.type != MediaWrapper.TYPE_DIR) {<br>
+ if (item.playCount > 0) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_mark_unplayed, context.getString(R.string.mark_as_not_played)))<br>
+ } else {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_mark_played, context.getString(R.string.mark_as_played)))<br>
+ }<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_play_all, context.getString(R.string.play_all)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_info, context.getString(<a href="http://R.string.info" rel="noreferrer" target="_blank">R.string.info</a>)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_share, context.getString(R.string.share)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_add_to_playlist, context.getString(R.string.add_to_playlist)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_delete, context.getString(R.string.delete)))<br>
+ } else {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_play_all, context.getString(R.string.play_all)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_add_to_playlist, context.getString(R.string.add_to_playlist)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_ban_folder, context.getString(R.string.ban_folder)))<br>
+ }<br>
+ if (item.type == MediaWrapper.TYPE_VIDEO) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_download_subtitles, context.getString(R.string.download_subtitles)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_play_as_audio, context.getString(R.string.play_as_audio)))<br>
+ }<br>
+ } else if (item is Storage) {<br>
+ // Storage actions if any<br>
+ }<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_open_context_menu, context.getString(R.string.more_actions)))<br>
+ }<br>
+<br>
+ override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {<br>
+ val option = when (action) {<br>
+ R.id.accessibility_action_add_favorite -> ContextOption.CTX_FAV_ADD<br>
+ R.id.accessibility_action_remove_favorite -> ContextOption.CTX_FAV_REMOVE<br>
+ R.id.accessibility_action_mark_played -> ContextOption.CTX_MARK_AS_PLAYED<br>
+ R.id.accessibility_action_mark_unplayed -> ContextOption.CTX_MARK_AS_UNPLAYED<br>
+ R.id.accessibility_action_append -> ContextOption.CTX_APPEND<br>
+ R.id.accessibility_action_play_next -> ContextOption.CTX_PLAY_NEXT<br>
+ R.id.accessibility_action_info -> ContextOption.CTX_INFORMATION<br>
+ R.id.accessibility_action_share -> ContextOption.CTX_SHARE<br>
+ R.id.accessibility_action_add_to_playlist -> ContextOption.CTX_ADD_TO_PLAYLIST<br>
+ R.id.accessibility_action_delete -> ContextOption.CTX_DELETE<br>
+ R.id.accessibility_action_download_subtitles -> ContextOption.CTX_DOWNLOAD_SUBTITLES<br>
+ R.id.accessibility_action_play_as_audio -> ContextOption.CTX_PLAY_AS_AUDIO<br>
+ R.id.accessibility_action_play_all -> ContextOption.CTX_PLAY_ALL<br>
+ R.id.accessibility_action_add_shortcut -> ContextOption.CTX_ADD_SHORTCUT<br>
+ R.id.accessibility_action_go_to_folder -> ContextOption.CTX_GO_TO_FOLDER<br>
+ R.id.accessibility_action_rename -> ContextOption.CTX_RENAME<br>
+ R.id.accessibility_action_ban_folder -> ContextOption.CTX_BAN_FOLDER<br>
+ R.id.accessibility_action_open_context_menu -> {<br>
+ onMoreClick(host)<br>
+ return true<br>
+ }<br>
+ else -> null<br>
+ }<br>
+<br>
+ if (option != null && browserContainer is CtxActionReceiver) {<br>
+ val position = layoutPosition<br>
+ if (position >= 0 && position < dataset.size) {<br>
+ (browserContainer as CtxActionReceiver).onCtxAction(position, option)<br>
+ }<br>
+ return true<br>
+ }<br>
+<br>
+ return super.performAccessibilityAction(host, action, args)<br>
+ }<br>
+ })<br>
+ }<br>
+<br>
init {<br>
bindingContainer.setHolder(this)<br>
if (AndroidUtil.isMarshMallowOrLater) itemView.setOnContextClickListener { v -><br>
@@ -275,6 +406,7 @@ open class BaseBrowserAdapter(val browserContainer: BrowserContainer<MediaLibrar<br>
<br>
bindingContainer.banIcon.onFocusChangeListener = focusChangeListener<br>
bindingContainer.container.onFocusChangeListener = focusChangeListener<br>
+ bindingContainer.moreIcon.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO<br>
}<br>
<br>
override fun selectView(selected: Boolean) {<br>
diff --git a/application/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.kt b/application/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.kt<br>
index 99092d919..8a856b977 100644<br>
--- a/application/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.kt<br>
+++ b/application/vlc-android/src/org/videolan/vlc/gui/video/VideoGridFragment.kt<br>
@@ -746,6 +746,9 @@ class VideoGridFragment : MediaBrowserFragment<VideosViewModel>(), SwipeRefreshL<br>
onLongClick(position)<br>
}<br>
}<br>
+ is VideoCtxAction -> {<br>
+ onCtxAction(position, option)<br>
+ }<br>
}<br>
}<br>
<br>
@@ -806,3 +809,4 @@ class VideoClick(val position: Int, val item: MediaLibraryItem) : VideoAction()<br>
class VideoLongClick(val position: Int, val item: MediaLibraryItem) : VideoAction()<br>
class VideoCtxClick(val position: Int, val item: MediaLibraryItem) : VideoAction()<br>
class VideoImageClick(val position: Int, val item: MediaLibraryItem) : VideoAction()<br>
+class VideoCtxAction(val position: Int, val option: ContextOption) : VideoAction()<br>
diff --git a/application/vlc-android/src/org/videolan/vlc/gui/video/VideoListAdapter.kt b/application/vlc-android/src/org/videolan/vlc/gui/video/VideoListAdapter.kt<br>
index 9e3a48292..1cb0602bb 100644<br>
--- a/application/vlc-android/src/org/videolan/vlc/gui/video/VideoListAdapter.kt<br>
+++ b/application/vlc-android/src/org/videolan/vlc/gui/video/VideoListAdapter.kt<br>
@@ -53,6 +53,17 @@ import org.videolan.vlc.media.isOTG<br>
import org.videolan.vlc.media.isSD<br>
import org.videolan.vlc.util.*<br>
import org.videolan.vlc.viewmodels.mobile.VideoGroupingType<br>
+import android.os.Bundle<br>
+import androidx.core.view.AccessibilityDelegateCompat<br>
+import androidx.core.view.ViewCompat<br>
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat<br>
+import <a href="http://androidx.appcompat.app" target="_blank">androidx.appcompat.app</a>.AppCompatActivity<br>
+import <a href="http://androidx.fragment.app" target="_blank">androidx.fragment.app</a>.FragmentActivity<br>
+import org.videolan.vlc.media.MediaUtils<br>
+import org.videolan.vlc.gui.helpers.UiTools.addToPlaylist<br>
+import org.videolan.vlc.gui.helpers.UiTools.showMediaInfo<br>
+import androidx.lifecycle.lifecycleScope<br>
+import org.videolan.tools.retrieveParent<br>
<br>
private const val TAG = "VLC/VideoListAdapter"<br>
<br>
@@ -88,7 +99,12 @@ class VideoListAdapter(private var isSeenMediaMarkerVisible: Boolean, private va<br>
}<br>
<br>
override fun onBindViewHolder(holder: ViewHolder, position: Int) {<br>
- val item = getItem(position) ?: return<br>
+ val item = getItem(position)<br>
+ if (item == null) {<br>
+ holder.updateAccessibilityDelegate(null)<br>
+ return<br>
+ }<br>
+ holder.updateAccessibilityDelegate(item)<br>
holder.binding.setVariable(BR.scaleType, ImageView.ScaleType.CENTER_CROP)<br>
fillView(holder, item)<br>
holder.binding.setVariable(BR.media, item)<br>
@@ -198,9 +214,137 @@ class VideoListAdapter(private var isSeenMediaMarkerVisible: Boolean, private va<br>
val title : TextView = itemView.findViewById(R.id.ml_item_title)<br>
val more : ImageView = itemView.findViewById(R.id.item_more)<br>
<br>
+ fun updateAccessibilityDelegate(item: MediaLibraryItem?) {<br>
+ if (item == null) {<br>
+ ViewCompat.setAccessibilityDelegate(itemView, null)<br>
+ return<br>
+ }<br>
+ ViewCompat.setAccessibilityDelegate(itemView, object : AccessibilityDelegateCompat() {<br>
+ override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {<br>
+ super.onInitializeAccessibilityNodeInfo(host, info)<br>
+ val context = itemView.context<br>
+<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_append, context.getString(R.string.append)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_play_next, context.getString(R.string.insert_next)))<br>
+<br>
+ if (item.isFavorite) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_remove_favorite, context.getString(R.string.favorites_remove)))<br>
+ } else {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_add_favorite, context.getString(R.string.favorites_add)))<br>
+ }<br>
+<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_add_shortcut, context.getString(R.string.create_shortcut)))<br>
+<br>
+ if (item is MediaWrapper) {<br>
+ if (item.seen > 0) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_mark_unplayed, context.getString(R.string.mark_as_not_played)))<br>
+ } else {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_mark_played, context.getString(R.string.mark_as_played)))<br>
+ }<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_play_all, context.getString(R.string.play_all)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_play_as_audio, context.getString(R.string.play_as_audio)))<br>
+ if (item.time > 0L) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_play_from_start, context.getString(R.string.play_from_start)))<br>
+ }<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_info, context.getString(<a href="http://R.string.info" rel="noreferrer" target="_blank">R.string.info</a>)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_share, context.getString(R.string.share)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_add_to_playlist, context.getString(R.string.add_to_playlist)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_delete, context.getString(R.string.delete)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_rename, context.getString(R.string.rename)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_download_subtitles, context.getString(R.string.download_subtitles)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_add_group, context.getString(R.string.add_to_group)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_group_similar, context.getString(R.string.group_similar)))<br>
+ if (item.uri.retrieveParent() != null) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_go_to_folder, context.getString(R.string.go_to_folder)))<br>
+ }<br>
+ } else if (item is VideoGroup || item is Folder) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_mark_unplayed, context.getString(R.string.mark_all_as_not_played)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_mark_played, context.getString(R.string.mark_all_as_played)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_play_all, context.getString(R.string.play_all)))<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_add_to_playlist, context.getString(R.string.add_to_playlist)))<br>
+ if (item is Folder) {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_ban_folder, context.getString(R.string.ban_folder)))<br>
+ } else {<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_rename, context.getString(R.string.rename_group)))<br>
+ }<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_delete, context.getString(R.string.delete)))<br>
+ }<br>
+ info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat(<br>
+ R.id.accessibility_action_open_context_menu, context.getString(R.string.more_actions)))<br>
+ }<br>
+<br>
+ override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {<br>
+ val option = when (action) {<br>
+ R.id.accessibility_action_append -> ContextOption.CTX_APPEND<br>
+ R.id.accessibility_action_play_next -> ContextOption.CTX_PLAY_NEXT<br>
+ R.id.accessibility_action_info -> ContextOption.CTX_INFORMATION<br>
+ R.id.accessibility_action_share -> ContextOption.CTX_SHARE<br>
+ R.id.accessibility_action_add_to_playlist -> ContextOption.CTX_ADD_TO_PLAYLIST<br>
+ R.id.accessibility_action_delete -> ContextOption.CTX_DELETE<br>
+ R.id.accessibility_action_download_subtitles -> ContextOption.CTX_DOWNLOAD_SUBTITLES<br>
+ R.id.accessibility_action_play_as_audio -> ContextOption.CTX_PLAY_AS_AUDIO<br>
+ R.id.accessibility_action_play_from_start -> ContextOption.CTX_PLAY_FROM_START<br>
+ R.id.accessibility_action_play_all -> ContextOption.CTX_PLAY_ALL<br>
+ R.id.accessibility_action_add_favorite -> ContextOption.CTX_FAV_ADD<br>
+ R.id.accessibility_action_remove_favorite -> ContextOption.CTX_FAV_REMOVE<br>
+ R.id.accessibility_action_add_shortcut -> ContextOption.CTX_ADD_SHORTCUT<br>
+ R.id.accessibility_action_add_group -> ContextOption.CTX_ADD_GROUP<br>
+ R.id.accessibility_action_group_similar -> ContextOption.CTX_GROUP_SIMILAR<br>
+ R.id.accessibility_action_go_to_folder -> ContextOption.CTX_GO_TO_FOLDER<br>
+ R.id.accessibility_action_rename -> if (item is VideoGroup) ContextOption.CTX_RENAME_GROUP else ContextOption.CTX_RENAME<br>
+ R.id.accessibility_action_ban_folder -> ContextOption.CTX_BAN_FOLDER<br>
+ R.id.accessibility_action_mark_played -> if (item is MediaWrapper || item.itemType == MediaLibraryItem.TYPE_MEDIA) ContextOption.CTX_MARK_AS_PLAYED else ContextOption.CTX_MARK_ALL_AS_PLAYED<br>
+ R.id.accessibility_action_mark_unplayed -> if (item is MediaWrapper || item.itemType == MediaLibraryItem.TYPE_MEDIA) ContextOption.CTX_MARK_AS_UNPLAYED else ContextOption.CTX_MARK_ALL_AS_UNPLAYED<br>
+ R.id.accessibility_action_open_context_menu -> {<br>
+ onMoreClick(host)<br>
+ return true<br>
+ }<br>
+ else -> null<br>
+ }<br>
+<br>
+ if (option != null) {<br>
+ val position = layoutPosition<br>
+ if (isPositionValid(position)) {<br>
+ eventsChannel.trySend(VideoCtxAction(position, option))<br>
+ }<br>
+ return true<br>
+ }<br>
+<br>
+ return super.performAccessibilityAction(host, action, args)<br>
+ }<br>
+ })<br>
+ }<br>
+<br>
init {<br>
binding.setVariable(BR.holder, this)<br>
binding.setVariable(BR.cover, UiTools.getDefaultVideoDrawable(itemView.context))<br>
+ more.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO<br>
if (AndroidUtil.isMarshMallowOrLater)<br>
itemView.setOnContextClickListener { v -><br>
onMoreClick(v)<br>
-- <br>
2.52.0.windows.1<br>
<br>
</blockquote></div>