Add accessibility tags (#1054)

* adding accessibility id to new conversation button

* adding accessibility ids and strings for testing

* updating id tags for new conversation buttons

* accessibility tags for create contact test

* adding ids for message requests, config message, requests banner and conversation view

* adding more tags to settings page

* adding tags to different resolutions for landing page

* updating display name in settings to include accessibility id

* found some stashed changes which i forgot about

* more stashed changes

* adding tags to layout sw400dp

* closed group testing, delete/unsend message testing and selecting contacts tag

* adding tags for message body, selecting contacts for group creation, deleted message config, unsend message modal, and trash icon

* added tags for disappearing messages menu option, time selector, clock icon, confirm of change (ok)and control message

* add test for block user

* docs: Adding in accessibility ID's for Appium testing

* accessibility tags for conversation options, profile picture/settings and block options

* Add content descriptions for better accessibility

* Add more content descriptions

* Add timer icon content description

* Update profile picture content description

* Adding accessibility ids to new conversation creation screen and to message notification settings

* fix: content descriptions in their correct places and prevent a crash

* build: update build number

* build: update build number

* Adding back in FrameLayout, making changes as request

* Fixed viewPager move

* Fixing changes as requested by Jubb

* Adding content descriptions to mentions list, voice message settings, link preview permissions, closed group menu and slow mode notifications option

* Adds content descriptions to blocked contacts heading in conversations settings, individual contacts in blocked contacts page and to empty message requests folder

* Adds content descriptions to empty message request folder and text input box for username on settings page

---------

Co-authored-by: charles <charles@oxen.io>
Co-authored-by: hjubb <hjubb@users.noreply.github.com>
This commit is contained in:
wafflesvsfrankie
2023-04-04 11:33:17 +10:00
committed by GitHub
parent cfdc3dc24d
commit 9ad73139b6
74 changed files with 423 additions and 83 deletions

View File

@@ -5,8 +5,9 @@ import androidx.annotation.AttrRes
/**
* Represents an action to be rendered
*/
data class ActionItem(
data class ActionItem @JvmOverloads constructor(
@AttrRes val iconRes: Int,
val title: CharSequence,
val action: Runnable
val action: Runnable,
val contentDescription: String? = null
)

View File

@@ -77,6 +77,7 @@ class ContextMenuList(recyclerView: RecyclerView, onItemClick: () -> Unit) {
context.theme.resolveAttribute(model.item.iconRes, typedValue, true)
icon.setImageDrawable(ContextCompat.getDrawable(context, typedValue.resourceId))
}
itemView.contentDescription = model.item.contentDescription
title.text = model.item.title
itemView.setOnClickListener {
model.item.action.run()

View File

@@ -781,7 +781,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
val recipient = viewModel.recipient ?: return
if (!isShowingMentionCandidatesView) {
additionalContentContainer.removeAllViews()
val view = MentionCandidatesView(this)
val view = MentionCandidatesView(this).apply {
contentDescription = context.getString(R.string.AccessibilityId_mentions_list)
}
view.glide = glide
view.onCandidateSelected = { handleMentionSelected(it) }
additionalContentContainer.addView(view)
@@ -958,7 +960,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
override fun block(deleteThread: Boolean) {
val title = R.string.RecipientPreferenceActivity_block_this_contact_question
val message = R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact
AlertDialog.Builder(this)
val dialog = AlertDialog.Builder(this)
.setTitle(title)
.setMessage(message)
.setNegativeButton(android.R.string.cancel, null)
@@ -969,6 +971,8 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
finish()
}
}.show()
val button = dialog.getButton(DialogInterface.BUTTON_POSITIVE)
button.setContentDescription("Confirm block")
}
override fun copySessionID(sessionId: String) {

View File

@@ -660,10 +660,14 @@ public final class ConversationReactionOverlay extends FrameLayout {
String userPublicKey = TextSecurePreferences.getLocalNumber(getContext());
// Select message
items.add(new ActionItem(R.attr.menu_select_icon, getContext().getResources().getString(R.string.conversation_context__menu_select), () -> handleActionItemClicked(Action.SELECT)));
items.add(new ActionItem(R.attr.menu_select_icon, getContext().getResources().getString(R.string.conversation_context__menu_select), () -> handleActionItemClicked(Action.SELECT),
getContext().getResources().getString(R.string.AccessibilityId_select)));
// Reply
if (!message.isPending() && !message.isFailed()) {
items.add(new ActionItem(R.attr.menu_reply_icon, getContext().getResources().getString(R.string.conversation_context__menu_reply), () -> handleActionItemClicked(Action.REPLY)));
items.add(
new ActionItem(R.attr.menu_reply_icon, getContext().getResources().getString(R.string.conversation_context__menu_reply), () -> handleActionItemClicked(Action.REPLY),
getContext().getResources().getString(R.string.AccessibilityId_reply_message))
);
}
// Copy message text
if (!containsControlMessage && hasText) {
@@ -671,11 +675,17 @@ public final class ConversationReactionOverlay extends FrameLayout {
}
// Copy Session ID
if (recipient.isGroupRecipient() && !recipient.isOpenGroupRecipient() && !message.getRecipient().getAddress().toString().equals(userPublicKey)) {
items.add(new ActionItem(R.attr.menu_copy_icon, getContext().getResources().getString(R.string.activity_conversation_menu_copy_session_id), () -> handleActionItemClicked(Action.COPY_SESSION_ID)));
items.add(new ActionItem(
R.attr.menu_copy_icon, getContext().getResources().getString(R.string.activity_conversation_menu_copy_session_id), () -> handleActionItemClicked(Action.COPY_SESSION_ID))
);
}
// Delete message
if (ConversationMenuItemHelper.userCanDeleteSelectedItems(getContext(), message, openGroup, userPublicKey, blindedPublicKey)) {
items.add(new ActionItem(R.attr.menu_trash_icon, getContext().getResources().getString(R.string.delete), () -> handleActionItemClicked(Action.DELETE)));
items.add(new ActionItem(R.attr.menu_trash_icon, getContext().getResources().getString(R.string.delete),
() -> handleActionItemClicked(Action.DELETE),
getContext().getResources().getString(R.string.AccessibilityId_delete_message)
)
);
}
// Ban user
if (ConversationMenuItemHelper.userCanBanSelectedUsers(getContext(), message, openGroup, userPublicKey, blindedPublicKey)) {
@@ -695,7 +705,9 @@ public final class ConversationReactionOverlay extends FrameLayout {
}
// Save media
if (message.isMms() && ((MediaMmsMessageRecord)message).containsMediaSlide()) {
items.add(new ActionItem(R.attr.menu_save_icon, getContext().getResources().getString(R.string.conversation_context_image__save_attachment), () -> handleActionItemClicked(Action.DOWNLOAD)));
items.add(new ActionItem(R.attr.menu_save_icon, getContext().getResources().getString(R.string.conversation_context_image__save_attachment), () -> handleActionItemClicked(Action.DOWNLOAD),
getContext().getResources().getString(R.string.AccessibilityId_save_attachment))
);
}
backgroundView.setVisibility(View.VISIBLE);

View File

@@ -57,9 +57,9 @@ class InputBar : RelativeLayout, InputBarEditTextDelegate, QuoteViewDelegate, Li
val attachmentButtonsContainerHeight: Int
get() = binding.attachmentsButtonContainer.height
private val attachmentsButton by lazy { InputBarButton(context, R.drawable.ic_plus_24) }
private val microphoneButton by lazy { InputBarButton(context, R.drawable.ic_microphone) }
private val sendButton by lazy { InputBarButton(context, R.drawable.ic_arrow_up, true) }
private val attachmentsButton by lazy { InputBarButton(context, R.drawable.ic_plus_24).apply { contentDescription = context.getString(R.string.AccessibilityId_attachments_button)} }
private val microphoneButton by lazy { InputBarButton(context, R.drawable.ic_microphone).apply { contentDescription = context.getString(R.string.AccessibilityId_microphone_button)} }
private val sendButton by lazy { InputBarButton(context, R.drawable.ic_arrow_up, true).apply { contentDescription = context.getString(R.string.AccessibilityId_send_message_button)} }
// region Lifecycle
constructor(context: Context) : super(context) { initialize() }

View File

@@ -7,6 +7,7 @@ import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ListView
import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R
import org.session.libsession.messaging.mentions.Mention
import org.thoughtcrime.securesms.database.LokiThreadDatabase
import org.thoughtcrime.securesms.mms.GlideRequests
@@ -41,7 +42,9 @@ class MentionCandidatesView(context: Context, attrs: AttributeSet?, defStyleAttr
override fun getItem(position: Int): Mention { return candidates[position] }
override fun getView(position: Int, cellToBeReused: View?, parent: ViewGroup): View {
val cell = cellToBeReused as MentionCandidateView? ?: MentionCandidateView(context)
val cell = cellToBeReused as MentionCandidateView? ?: MentionCandidateView(context).apply {
contentDescription = context.getString(R.string.AccessibilityId_contact)
}
val mentionCandidate = getItem(position)
cell.glide = glide
cell.candidate = mentionCandidate

View File

@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2.menus
import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.graphics.BitmapFactory
import android.graphics.PorterDuff
@@ -185,7 +186,7 @@ object ConversationMenuHelper {
private fun call(context: Context, thread: Recipient) {
if (!TextSecurePreferences.isCallNotificationsEnabled(context)) {
AlertDialog.Builder(context)
val dialog = AlertDialog.Builder(context)
.setTitle(R.string.ConversationActivity_call_title)
.setMessage(R.string.ConversationActivity_call_prompt)
.setPositiveButton(R.string.activity_settings_title) { _, _ ->
@@ -194,7 +195,10 @@ object ConversationMenuHelper {
}
.setNeutralButton(R.string.cancel) { d, _ ->
d.dismiss()
}.show()
}.create()
dialog.getButton(DialogInterface.BUTTON_POSITIVE)?.contentDescription = context.getString(R.string.AccessibilityId_settings)
dialog.getButton(DialogInterface.BUTTON_NEGATIVE)?.contentDescription = context.getString(R.string.AccessibilityId_cancel_button)
dialog.show()
return
}

View File

@@ -31,6 +31,7 @@ class ControlMessageView : LinearLayout {
binding.dateBreakTextView.showDateBreak(message, previous)
binding.iconImageView.visibility = View.GONE
var messageBody: CharSequence = message.getDisplayBody(context)
binding.root.contentDescription= null
when {
message.isExpirationTimerUpdate -> {
binding.iconImageView.setImageDrawable(
@@ -46,6 +47,7 @@ class ControlMessageView : LinearLayout {
}
message.isMessageRequestResponse -> {
messageBody = context.getString(R.string.message_requests_accepted)
binding.root.contentDescription=context.getString(R.string.AccessibilityId_message_request_config_message)
}
message.isCallLog -> {
val drawable = when {

View File

@@ -13,6 +13,9 @@ import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.View
import android.widget.LinearLayout
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
@@ -194,7 +197,7 @@ class VisibleMessageView : LinearLayout {
binding.dateBreakTextView.isVisible = showDateBreak
// Message status indicator
if (message.isOutgoing) {
val (iconID, iconColor, textId) = getMessageStatusImage(message)
val (iconID, iconColor, textId, contentDescription) = getMessageStatusImage(message)
if (textId != null) {
binding.messageStatusTextView.setText(textId)
@@ -209,6 +212,7 @@ class VisibleMessageView : LinearLayout {
}
binding.messageStatusImageView.setImageDrawable(drawable)
}
binding.messageStatusImageView.contentDescription = contentDescription
val lastMessageID = mmsSmsDb.getLastMessageID(message.threadId)
binding.messageStatusTextView.isVisible = (
@@ -283,17 +287,43 @@ class VisibleMessageView : LinearLayout {
}
}
private fun getMessageStatusImage(message: MessageRecord): Triple<Int?,Int?,Int?> {
data class MessageStatusInfo(@DrawableRes val iconId: Int?,
@ColorInt val iconTint: Int?,
@StringRes val messageText: Int?,
val contentDescription: String?)
private fun getMessageStatusImage(message: MessageRecord): MessageStatusInfo {
return when {
!message.isOutgoing -> Triple(null, null, null)
!message.isOutgoing -> MessageStatusInfo(null,
null,
null,
null)
message.isFailed ->
Triple(R.drawable.ic_delivery_status_failed, resources.getColor(R.color.destructive, context.theme), R.string.delivery_status_failed)
MessageStatusInfo(
R.drawable.ic_delivery_status_failed,
resources.getColor(R.color.destructive, context.theme),
R.string.delivery_status_failed,
null
)
message.isPending ->
Triple(R.drawable.ic_delivery_status_sending, context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sending)
MessageStatusInfo(
R.drawable.ic_delivery_status_sending,
context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sending,
context.getString(R.string.AccessibilityId_message_sent_status_pending)
)
message.isRead ->
Triple(R.drawable.ic_delivery_status_read, context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_read)
MessageStatusInfo(
R.drawable.ic_delivery_status_read,
context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_read,
null
)
else ->
Triple(R.drawable.ic_delivery_status_sent, context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sent)
MessageStatusInfo(
R.drawable.ic_delivery_status_sent,
context.getColorFromAttr(R.attr.message_status_color),
R.string.delivery_status_sent,
context.getString(R.string.AccessibilityId_message_sent_status_tick)
)
}
}

View File

@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.permissions;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
@@ -12,6 +13,7 @@ import android.util.DisplayMetrics;
import android.view.Display;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Button;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
@@ -162,12 +164,13 @@ public class Permissions {
@SuppressWarnings("ConstantConditions")
private void executePermissionsRequestWithRationale(PermissionsRequest request) {
RationaleDialog.createFor(permissionObject.getContext(), rationaleDialogMessage, rationalDialogHeader)
.setPositiveButton(R.string.Permissions_continue, (dialog, which) -> executePermissionsRequest(request))
.setNegativeButton(R.string.Permissions_not_now, (dialog, which) -> executeNoPermissionsRequest(request))
.show()
.getWindow()
.setLayout((int)(permissionObject.getWindowWidth() * .75), ViewGroup.LayoutParams.WRAP_CONTENT);
AlertDialog dialog = RationaleDialog.createFor(permissionObject.getContext(), rationaleDialogMessage, rationalDialogHeader)
.setPositiveButton(R.string.Permissions_continue, (d, which) -> executePermissionsRequest(request))
.setNegativeButton(R.string.Permissions_not_now, (d, which) -> executeNoPermissionsRequest(request))
.show();
dialog.getWindow().setLayout((int)(permissionObject.getWindowWidth() * .75), ViewGroup.LayoutParams.WRAP_CONTENT);
Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
positiveButton.setContentDescription("Continue");
}
private void executePermissionsRequest(PermissionsRequest request) {
@@ -353,12 +356,17 @@ public class Permissions {
Context context = this.context.get();
if (context != null) {
new AlertDialog.Builder(context, R.style.ThemeOverlay_Session_AlertDialog)
AlertDialog alertDialog = new AlertDialog.Builder(context, R.style.ThemeOverlay_Session_AlertDialog)
.setTitle(R.string.Permissions_permission_required)
.setMessage(message)
.setPositiveButton(R.string.Permissions_continue, (dialog, which) -> context.startActivity(getApplicationSettingsIntent(context)))
.setNegativeButton(android.R.string.cancel, null)
.show();
.create();
Button positiveButton = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
if (positiveButton != null) {
positiveButton.setContentDescription(context.getString(R.string.AccessibilityId_continue));
}
alertDialog.show();
}
}
}

View File

@@ -5,10 +5,12 @@ import android.app.Activity;
import android.app.AlertDialog;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -179,7 +181,7 @@ public class PrivacySettingsPreferenceFragment extends ListSummaryPreferenceFrag
boolean val = (boolean) newValue;
if (val) {
// check if we've shown the info dialog and check for microphone permissions
new AlertDialog.Builder(new ContextThemeWrapper(context.requireContext(), R.style.ThemeOverlay_Session_AlertDialog))
AlertDialog dialog = new AlertDialog.Builder(new ContextThemeWrapper(context.requireContext(), R.style.ThemeOverlay_Session_AlertDialog))
.setTitle(R.string.dialog_voice_video_title)
.setMessage(R.string.dialog_voice_video_message)
.setPositiveButton(R.string.dialog_link_preview_enable_button_title, (d, w) -> {
@@ -189,7 +191,9 @@ public class PrivacySettingsPreferenceFragment extends ListSummaryPreferenceFrag
})
.show();
return false;
Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
positiveButton.setContentDescription("Enable");
return false;
} else {
return true;
}