fix: Ensure items in accessibility dialogs are clickable (#1112)
The copy button meant that some dialogs did not return the item click. Fix this by having the adapter listen for clicks and forward them on. Pre-emptively move the adapter to core.ui, as it's going to be useful for the other accessiblity delegates. Fixes #1108
This commit is contained in:
parent
654a81a136
commit
f2ed6a0dab
|
@ -1,21 +1,13 @@
|
|||
package app.pachli.util
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Spannable
|
||||
import android.text.style.URLSpan
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.ContextCompat.getSystemService
|
||||
import androidx.core.view.AccessibilityDelegateCompat
|
||||
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
|
||||
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
|
||||
|
@ -26,7 +18,7 @@ import app.pachli.adapter.FilterableStatusViewHolder
|
|||
import app.pachli.adapter.StatusBaseViewHolder
|
||||
import app.pachli.core.activity.openLink
|
||||
import app.pachli.core.network.model.Status.Companion.MAX_MEDIA_ATTACHMENTS
|
||||
import app.pachli.databinding.SimpleListItem1CopyButtonBinding
|
||||
import app.pachli.core.ui.ArrayAdapterWithCopyButton
|
||||
import app.pachli.interfaces.StatusActionListener
|
||||
import app.pachli.viewdata.IStatusViewData
|
||||
import app.pachli.viewdata.NotificationViewData
|
||||
|
@ -222,8 +214,9 @@ class ListStatusAccessibilityDelegate<T : IStatusViewData>(
|
|||
ArrayAdapterWithCopyButton(
|
||||
host.context,
|
||||
textLinks,
|
||||
),
|
||||
) { _, which -> host.context.openLink(links[which].link) }
|
||||
) { position -> host.context.openLink(links[position].link) },
|
||||
null,
|
||||
)
|
||||
.show()
|
||||
.let { forceFocus(it.listView) }
|
||||
}
|
||||
|
@ -241,10 +234,11 @@ class ListStatusAccessibilityDelegate<T : IStatusViewData>(
|
|||
ArrayAdapterWithCopyButton(
|
||||
host.context,
|
||||
stringMentions,
|
||||
),
|
||||
) { _, which ->
|
||||
statusActionListener.onViewAccount(mentions[which].id)
|
||||
}
|
||||
) { position ->
|
||||
statusActionListener.onViewAccount(mentions[position].id)
|
||||
},
|
||||
null,
|
||||
)
|
||||
.show()
|
||||
.let { forceFocus(it.listView) }
|
||||
}
|
||||
|
@ -258,10 +252,11 @@ class ListStatusAccessibilityDelegate<T : IStatusViewData>(
|
|||
ArrayAdapterWithCopyButton(
|
||||
host.context,
|
||||
tags,
|
||||
),
|
||||
) { _, which ->
|
||||
statusActionListener.onViewTag(tags[which].toString())
|
||||
}
|
||||
) { position ->
|
||||
statusActionListener.onViewTag(tags[position].toString())
|
||||
},
|
||||
null,
|
||||
)
|
||||
.show()
|
||||
.let { forceFocus(it.listView) }
|
||||
}
|
||||
|
@ -413,40 +408,3 @@ class ListStatusAccessibilityDelegate<T : IStatusViewData>(
|
|||
|
||||
private data class LinkSpanInfo(val text: String, val link: String)
|
||||
}
|
||||
|
||||
/**
|
||||
* An [ArrayAdapter] that shows a "copy" button next to each item. When clicked
|
||||
* the text of the item is copied to the clipboard and a toast is shown (if
|
||||
* appropriate).
|
||||
*/
|
||||
private class ArrayAdapterWithCopyButton<T : CharSequence>(
|
||||
context: Context,
|
||||
items: List<T>,
|
||||
) : ArrayAdapter<T>(context, R.layout.simple_list_item_1_copy_button, items) {
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val binding = if (convertView == null) {
|
||||
SimpleListItem1CopyButtonBinding.inflate(LayoutInflater.from(context), parent, false)
|
||||
} else {
|
||||
SimpleListItem1CopyButtonBinding.bind(convertView)
|
||||
}
|
||||
|
||||
getItem(position)?.let { text ->
|
||||
binding.text1.text = text
|
||||
|
||||
binding.copy.setOnClickListener {
|
||||
val clipboard = getSystemService(context, ClipboardManager::class.java) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("", text)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.item_copied),
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
}
|
||||
|
|
|
@ -782,8 +782,6 @@
|
|||
<string name="pref_title_confirm_status_language">Revisar idioma de la publicación antes de publicar</string>
|
||||
<string name="compose_warn_language_dialog_accept_and_dont_ask_fmt">Publicar como está (%1$s) y no volver a preguntar</string>
|
||||
<string name="upload_failed_msg_fmt">Se intentará subir nuevamente cuando envíes la publicación. Si vuelve a fallar, la publicación se guardará en tus borradores.\n\nEl error fue: %1$s</string>
|
||||
<string name="item_copied">Texto copiado</string>
|
||||
<string name="action_copy_item">Copiar ítem</string>
|
||||
<plurals name="notification_severed_relationships_summary_followers_fmt">
|
||||
<item quantity="one">%1$s seguidor eliminado</item>
|
||||
<item quantity="many">%1$s seguidores eliminados</item>
|
||||
|
@ -798,4 +796,4 @@
|
|||
<string name="main_viewmodel_error_set_active_account">El inicio de sesión falló con el siguiente error:\n\n%1$s</string>
|
||||
<string name="main_viewmodel_error_refresh_account">La actualización de la cuenta falló con el siguiente error:\n\n%1$s\n\nPuedes continuar, pero tus listas y filtros pueden estar incompletos.</string>
|
||||
<string name="action_relogin">Volver a iniciar sesión</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
@ -765,6 +765,4 @@
|
|||
<string name="conversation_0_recipients">Ei muita osanottajia</string>
|
||||
<string name="compose_warn_language_dialog_accept_and_dont_ask_fmt">Julkaise muutoksetta (%1$s) äläkä kysy uudelleen</string>
|
||||
<string name="pref_title_confirm_status_language">Tarkasta julkaisun kieli ennen julkaisemista</string>
|
||||
<string name="action_copy_item">Kopioi kohde</string>
|
||||
<string name="item_copied">Teksti kopioitu</string>
|
||||
</resources>
|
||||
|
|
|
@ -527,8 +527,6 @@
|
|||
<string name="search_operator_where_all">Gach post ▾</string>
|
||||
<string name="search_operator_where_library">Do leabharlann</string>
|
||||
<string name="search_operator_where_dialog_all_hint">Poist i do leabharlann agus i do phoist phoiblí</string>
|
||||
<string name="action_copy_item">Cóipeáil mír</string>
|
||||
<string name="item_copied">Cóipeáladh téacs</string>
|
||||
<string name="upload_failed_msg_fmt">Déanfar an t-uaslódáil a aisghabháil nuair a sheolann tú an post. Má theipeann air arís sábhálfar an post i do dhréachtaí.\n\nEarráid a bhí ann: %1$s</string>
|
||||
<string name="upload_failed_modify_attachment">Mionathraigh iatán</string>
|
||||
<string name="duration_30_days">laethanta 30</string>
|
||||
|
|
|
@ -850,8 +850,6 @@
|
|||
<string name="search_operator_where_dialog_public">Public posts</string>
|
||||
<string name="search_operator_where_dialog_public_hint">Public, searchable posts known by server</string>
|
||||
|
||||
<string name="action_copy_item">Copy item</string>
|
||||
<string name="item_copied">Text copied</string>
|
||||
|
||||
<string name="upload_failed_msg_fmt">The upload will be retried when you send the post. If it fails again the post will be saved in your drafts.\n\nThe error was: %1$s</string>
|
||||
<string name="upload_failed_modify_attachment">Modify attachment</string>
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright 2024 Pachli Association
|
||||
*
|
||||
* This file is a part of Pachli.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Pachli; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package app.pachli.core.ui
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import app.pachli.core.ui.databinding.SimpleListItem1CopyButtonBinding
|
||||
|
||||
/**
|
||||
* An [ArrayAdapter] that shows a "copy" button next to each item.
|
||||
*
|
||||
* If the "copy" button is clicked the text of the item is copied to the clipboard
|
||||
* and a toast is shown (if appropriate).
|
||||
*
|
||||
* If the item is clicked then [listener] is called with the position of the
|
||||
* clicked item.
|
||||
*/
|
||||
class ArrayAdapterWithCopyButton<T : CharSequence>(
|
||||
context: Context,
|
||||
items: List<T>,
|
||||
private val listener: OnClickListener,
|
||||
) : ArrayAdapter<T>(context, R.layout.simple_list_item_1_copy_button, android.R.id.text1, items) {
|
||||
fun interface OnClickListener {
|
||||
/** @param position Index of the item the user clicked. */
|
||||
fun onClick(position: Int)
|
||||
}
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val binding = if (convertView == null) {
|
||||
SimpleListItem1CopyButtonBinding.inflate(LayoutInflater.from(context), parent, false).apply {
|
||||
text1.setOnClickListener { listener.onClick(position) }
|
||||
|
||||
copy.setOnClickListener {
|
||||
getItem(position)?.let { text ->
|
||||
val clipboard = ContextCompat.getSystemService(
|
||||
context,
|
||||
ClipboardManager::class.java,
|
||||
) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("", text)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.item_copied),
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
SimpleListItem1CopyButtonBinding.bind(convertView)
|
||||
}
|
||||
|
||||
getItem(position)?.let { binding.text1.text = it }
|
||||
|
||||
return binding.root
|
||||
}
|
||||
}
|
|
@ -31,10 +31,10 @@
|
|||
android:id="@android:id/text1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall"
|
||||
android:focusable="true"
|
||||
tools:ignore="SelectableText"
|
||||
tools:text="#TestHashTag" />
|
||||
|
|
@ -14,4 +14,6 @@
|
|||
<string name="action_hashtags">Etiquetas</string>
|
||||
<string name="title_links_dialog">Enlaces</string>
|
||||
<string name="title_hashtags_dialog">Etiquetas</string>
|
||||
<string name="item_copied">Texto copiado</string>
|
||||
<string name="action_copy_item">Copiar ítem</string>
|
||||
</resources>
|
||||
|
|
|
@ -14,4 +14,6 @@
|
|||
<string name="action_hashtags">Aihetunnisteet</string>
|
||||
<string name="title_links_dialog">Linkit</string>
|
||||
<string name="title_hashtags_dialog">Aihetunnisteet</string>
|
||||
<string name="item_copied">Teksti kopioitu</string>
|
||||
<string name="action_copy_item">Kopioi kohde</string>
|
||||
</resources>
|
||||
|
|
|
@ -14,4 +14,6 @@
|
|||
<string name="title_hashtags_dialog">Haischlibeanna</string>
|
||||
<string name="action_refresh">Athnuaigh</string>
|
||||
<string name="url_domain_notifier">" (🔗 %s)"</string>
|
||||
</resources>
|
||||
<string name="item_copied">Cóipeáladh téacs</string>
|
||||
<string name="action_copy_item">Cóipeáil mír</string>
|
||||
</resources>
|
||||
|
|
|
@ -14,4 +14,6 @@
|
|||
<string name="action_hashtags">Hashtags</string>
|
||||
<string name="title_links_dialog">Links</string>
|
||||
<string name="title_hashtags_dialog">Hashtags</string>
|
||||
<string name="item_copied">Text copied</string>
|
||||
<string name="action_copy_item">Copy item</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue