fix: Improve accessibility of ComposeActivity bottomsheets and buttons (#548)

The previous bottomsheets did set a minimum height for the menu items,
so they were less than the recommended 48dp minimum. Fix that to improve
the overall accessibility.

Always highlight the "visibilty" icon, to make it clear that it's
something that is set (even if to the default).

Show the visibility icon on the "Toot" button as an additional reminder
to the user.

Other changes:

- Use the "filled" style for all icons (the visibility icons had the
"outlined" style)

- Use the `makeIcon` helper function.

- Use the `Status.Visibility` extension functions to determine the icon
for each visibility type, reducing code duplication.
This commit is contained in:
Nik Clayton 2024-03-21 16:24:31 +01:00 committed by GitHub
parent 0ad25bd617
commit 8ef227fd20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 83 additions and 114 deletions

View File

@ -1748,7 +1748,7 @@
errorLine2=" ~~~~~~~~">
<location
file="src/main/res/layout/activity_compose.xml"
line="364"
line="368"
column="10"/>
</issue>

View File

@ -99,6 +99,8 @@ import app.pachli.util.getInitialLanguages
import app.pachli.util.getLocaleList
import app.pachli.util.getMediaSize
import app.pachli.util.highlightSpans
import app.pachli.util.iconRes
import app.pachli.util.makeIcon
import app.pachli.util.modernLanguageCode
import app.pachli.util.setDrawableTint
import com.canhub.cropper.CropImage
@ -108,8 +110,8 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.color.MaterialColors
import com.google.android.material.snackbar.Snackbar
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.IconicsSize
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
import dagger.hilt.android.AndroidEntryPoint
import java.io.File
@ -515,24 +517,13 @@ class ComposeActivity :
displayTransientMessage(R.string.hint_media_description_missing)
}
val textColor = MaterialColors.getColor(binding.root, android.R.attr.textColorTertiary)
val cameraIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_camera_alt).apply {
colorInt = textColor
sizeDp = 18
}
val cameraIcon = makeIcon(this, GoogleMaterial.Icon.gmd_camera_alt, IconicsSize.dp(18))
binding.actionPhotoTake.setCompoundDrawablesRelativeWithIntrinsicBounds(cameraIcon, null, null, null)
val imageIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_image).apply {
colorInt = textColor
sizeDp = 18
}
val imageIcon = makeIcon(this, GoogleMaterial.Icon.gmd_image, IconicsSize.dp(18))
binding.actionPhotoPick.setCompoundDrawablesRelativeWithIntrinsicBounds(imageIcon, null, null, null)
val pollIcon = IconicsDrawable(this, GoogleMaterial.Icon.gmd_poll).apply {
colorInt = textColor
sizeDp = 18
}
val pollIcon = makeIcon(this, GoogleMaterial.Icon.gmd_poll, IconicsSize.dp(18))
binding.addPollTextActionTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(pollIcon, null, null, null)
binding.actionPhotoTake.visible(Intent(MediaStore.ACTION_IMAGE_CAPTURE).resolveActivity(packageManager) != null)
@ -754,15 +745,9 @@ class ComposeActivity :
private fun setStatusVisibility(visibility: Status.Visibility) {
binding.composeOptionsBottomSheet.setStatusVisibility(visibility)
binding.composeTootButton.setStatusVisibility(visibility)
binding.composeTootButton.setStatusVisibility(binding.composeTootButton, visibility)
val iconRes = when (visibility) {
Status.Visibility.PUBLIC -> R.drawable.ic_public_24dp
Status.Visibility.PRIVATE -> R.drawable.ic_lock_outline_24dp
Status.Visibility.DIRECT -> R.drawable.ic_email_24dp
Status.Visibility.UNLISTED -> R.drawable.ic_lock_open_24dp
else -> R.drawable.ic_lock_open_24dp
}
val iconRes = visibility.iconRes() ?: R.drawable.ic_lock_open_24dp
binding.composeToggleVisibilityButton.setImageResource(iconRes)
if (viewModel.editing) {
// Can't update visibility on published status

View File

@ -17,16 +17,13 @@
package app.pachli.components.compose.view
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.View
import app.pachli.R
import app.pachli.core.designsystem.R as DR
import app.pachli.core.network.model.Status
import app.pachli.util.iconDrawable
import com.google.android.material.button.MaterialButton
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.sizeDp
class TootButton
@JvmOverloads constructor(
@ -48,29 +45,16 @@ class TootButton
setPadding(padding, 0, padding, 0)
}
fun setStatusVisibility(visibility: Status.Visibility) {
fun setStatusVisibility(view: View, visibility: Status.Visibility) {
if (!smallStyle) {
icon = when (visibility) {
Status.Visibility.PUBLIC -> {
setText(R.string.action_send_public)
null
}
Status.Visibility.UNLISTED -> {
setText(R.string.action_send)
null
}
Status.Visibility.PRIVATE,
Status.Visibility.DIRECT,
-> {
setText(R.string.action_send)
IconicsDrawable(context, GoogleMaterial.Icon.gmd_lock).apply {
sizeDp = 18
colorInt = Color.WHITE
}
}
else -> {
null
}
icon = visibility.iconDrawable(view)
when (visibility) {
Status.Visibility.UNKNOWN -> { /* do nothing */ }
Status.Visibility.PUBLIC -> setText(R.string.action_send_public)
Status.Visibility.UNLISTED -> setText(R.string.action_send)
Status.Visibility.PRIVATE -> setText(R.string.action_send)
Status.Visibility.DIRECT -> setText(R.string.action_send)
}
}
}

View File

@ -54,6 +54,7 @@ import app.pachli.settings.switchPreference
import app.pachli.util.getInitialLanguages
import app.pachli.util.getLocaleList
import app.pachli.util.getPachliDisplayName
import app.pachli.util.iconRes
import app.pachli.util.makeIcon
import com.github.michaelbull.result.getOrElse
import com.google.android.material.snackbar.Snackbar
@ -207,9 +208,9 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
setSummaryProvider { entry }
val visibility = accountManager.activeAccount?.defaultPostPrivacy ?: Status.Visibility.PUBLIC
value = visibility.serverString()
setIcon(getIconForVisibility(visibility))
visibility.iconRes()?.let { setIcon(it) }
setOnPreferenceChangeListener { _, newValue ->
setIcon(getIconForVisibility(Status.Visibility.byString(newValue as String)))
Status.Visibility.byString(newValue as String).iconRes()?.let { setIcon(it) }
syncWithServer(visibility = newValue)
true
}
@ -344,17 +345,6 @@ class AccountPreferencesFragment : PreferenceFragmentCompat() {
}
}
@DrawableRes
private fun getIconForVisibility(visibility: Status.Visibility): Int {
return when (visibility) {
Status.Visibility.PRIVATE -> R.drawable.ic_lock_outline_24dp
Status.Visibility.UNLISTED -> R.drawable.ic_lock_open_24dp
else -> R.drawable.ic_public_24dp
}
}
@DrawableRes
private fun getIconForSensitivity(sensitive: Boolean): Int {
return if (sensitive) {

View File

@ -21,8 +21,10 @@ import android.graphics.Color
import androidx.annotation.Px
import com.google.android.material.color.MaterialColors
import com.mikepenz.iconics.IconicsDrawable
import com.mikepenz.iconics.IconicsSize
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
import com.mikepenz.iconics.utils.colorInt
import com.mikepenz.iconics.utils.size
import com.mikepenz.iconics.utils.sizePx
fun makeIcon(context: Context, icon: GoogleMaterial.Icon, @Px iconSize: Int): IconicsDrawable {
@ -31,3 +33,10 @@ fun makeIcon(context: Context, icon: GoogleMaterial.Icon, @Px iconSize: Int): Ic
colorInt = MaterialColors.getColor(context, androidx.appcompat.R.attr.colorControlNormal, Color.BLACK)
}
}
fun makeIcon(context: Context, icon: GoogleMaterial.Icon, iconSize: IconicsSize): IconicsDrawable {
return IconicsDrawable(context, icon).apply {
size = iconSize
colorInt = MaterialColors.getColor(context, androidx.appcompat.R.attr.colorControlNormal, Color.BLACK)
}
}

View File

@ -19,7 +19,9 @@ package app.pachli.util
import android.content.Context
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.appcompat.content.res.AppCompatResources
import app.pachli.R
import app.pachli.core.network.model.Status
@ -43,6 +45,24 @@ fun Status.Visibility?.description(context: Context): CharSequence {
return context.getString(resource)
}
@DrawableRes
fun Status.Visibility?.iconRes(): Int? {
this ?: return null
return when (this) {
Status.Visibility.PUBLIC -> R.drawable.ic_public_24dp
Status.Visibility.UNLISTED -> R.drawable.ic_lock_open_24dp
Status.Visibility.PRIVATE -> R.drawable.ic_lock_24dp
Status.Visibility.DIRECT -> R.drawable.ic_email_24dp
Status.Visibility.UNKNOWN -> return null
}
}
fun Status.Visibility?.iconDrawable(view: View): Drawable? {
val resource = iconRes() ?: return null
return AppCompatResources.getDrawable(view.context, resource)
}
/**
* @return An icon for this visibility scaled and coloured to match the text on [textView].
* Returns null if visibility is [Status.Visibility.UNKNOWN].
@ -50,19 +70,9 @@ fun Status.Visibility?.description(context: Context): CharSequence {
fun Status.Visibility?.icon(textView: TextView): Drawable? {
this ?: return null
val resource: Int = when (this) {
Status.Visibility.PUBLIC -> R.drawable.ic_public_24dp
Status.Visibility.UNLISTED -> R.drawable.ic_lock_open_24dp
Status.Visibility.PRIVATE -> R.drawable.ic_lock_outline_24dp
Status.Visibility.DIRECT -> R.drawable.ic_email_24dp
Status.Visibility.UNKNOWN -> return null
}
val visibilityDrawable = AppCompatResources.getDrawable(
textView.context,
resource,
) ?: return null
val drawable = iconDrawable(textView) ?: return null
val size = textView.textSize.toInt()
visibilityDrawable.setBounds(0, 0, size, size)
visibilityDrawable.setTint(textView.currentTextColor)
return visibilityDrawable
drawable.setBounds(0, 0, size, size)
drawable.setTint(textView.currentTextColor)
return drawable
}

View File

@ -1,7 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#fff" android:pathData="M4,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4M12,11L20,6H4L12,11M4,18H20V8.37L12,13.36L4,8.37V18Z" />
</vector>
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5L4,6l8,5 8,-5v2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#fff"
android:pathData="M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1s3.1,1.39 3.1,3.1v2L8.9,8L8.9,6zM18,20L6,20L6,10h12v10z" />
</vector>

View File

@ -198,7 +198,7 @@
android:background="?attr/colorSurface"
android:elevation="12dp"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingStart="24dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="52dp"
@ -210,28 +210,31 @@
android:id="@+id/actionPhotoTake"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="48dp"
android:drawablePadding="8dp"
android:padding="8dp"
android:text="@string/action_photo_take"
android:textSize="?attr/status_text_medium" />
android:textColor="?android:textColorTertiary" />
<TextView
android:id="@+id/actionPhotoPick"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="48dp"
android:drawablePadding="8dp"
android:padding="8dp"
android:text="@string/action_add_media"
android:textSize="?attr/status_text_medium" />
android:textColor="?android:textColorTertiary" />
<TextView
android:id="@+id/addPollTextActionTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="48dp"
android:drawablePadding="8dp"
android:padding="8dp"
android:text="@string/action_add_poll"
android:textSize="?attr/status_text_medium" />
android:textColor="?android:textColorTertiary" />
</LinearLayout>
<app.pachli.view.EmojiPicker
@ -309,6 +312,7 @@
android:layout_marginEnd="4dp"
android:contentDescription="@string/action_toggle_visibility"
android:padding="4dp"
app:tint="?attr/colorPrimary"
app:tooltipText="@string/action_toggle_visibility"
tools:src="@drawable/ic_public_24dp" />

View File

@ -9,8 +9,7 @@
android:id="@+id/publicRadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_weight="1"
android:minHeight="48dp"
android:button="@drawable/ic_public_24dp"
android:paddingStart="10dp"
android:paddingEnd="0dp"
@ -22,9 +21,7 @@
android:id="@+id/unlistedRadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_weight="1"
android:minHeight="48dp"
android:button="@drawable/ic_lock_open_24dp"
android:paddingStart="10dp"
android:paddingEnd="0dp"
@ -36,10 +33,8 @@
android:id="@+id/privateRadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_weight="1"
android:button="@drawable/ic_lock_outline_24dp"
android:minHeight="48dp"
android:button="@drawable/ic_lock_24dp"
android:paddingStart="10dp"
android:paddingEnd="0dp"
android:text="@string/visibility_private"
@ -50,8 +45,7 @@
android:id="@+id/directRadioButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_weight="1"
android:minHeight="48dp"
android:button="@drawable/ic_email_24dp"
android:paddingStart="10dp"
android:paddingEnd="0dp"
@ -59,4 +53,4 @@
android:textColor="?android:textColorTertiary"
app:buttonTint="@color/compound_button_color" />
</merge>
</merge>