mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-09-28 15:53:25 +01:00
Add missing content descriptions to media-send capture and image editor toolbars.
This commit is contained in:
+2
-1
@@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.signal.core.ui.compose.IconButtons
|
||||
import org.signal.core.ui.compose.NightPreview
|
||||
@@ -96,7 +97,7 @@ private fun LinkButton(
|
||||
Icon(
|
||||
imageVector = SignalIcons.Link.imageVector,
|
||||
tint = SignalTheme.colors.colorOnCustom,
|
||||
contentDescription = null // TODO [media-send]
|
||||
contentDescription = stringResource(R.string.TextStoryBar__add_link)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+15
-2
@@ -13,6 +13,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.signal.core.ui.compose.FoldablePortraitDayPreview
|
||||
import org.signal.core.ui.compose.FoldablePortraitNightPreview
|
||||
@@ -27,6 +28,7 @@ import org.signal.core.util.next
|
||||
import org.signal.imageeditor.core.model.EditorModel
|
||||
import org.signal.mediasend.EditorState
|
||||
import org.signal.mediasend.MediaSendFlowState
|
||||
import org.signal.mediasend.R
|
||||
import org.signal.mediasend.rememberPreviewState
|
||||
import org.signal.mediasend.screens.edit.ImageController
|
||||
import org.signal.mediasend.screens.edit.MediaEditScreenDialogs
|
||||
@@ -177,6 +179,8 @@ private fun ImageEditorCropAndResizeToolbar(
|
||||
|
||||
val cropLockImageVector = SignalIcons.CropLock.imageVector
|
||||
val cropUnlockImageVector = SignalIcons.CropUnlock.imageVector
|
||||
val cropLockContentDescription = stringResource(R.string.ImageEditorToolbar__aspect_ratio_locked)
|
||||
val cropUnlockContentDescription = stringResource(R.string.ImageEditorToolbar__aspect_ratio_unlocked)
|
||||
|
||||
IconCrossfadeToggleButton(
|
||||
target = if (imageEditorController.isCropAspectRatioLocked) CropLock.LOCKED else CropLock.UNLOCKED,
|
||||
@@ -193,6 +197,14 @@ private fun ImageEditorCropAndResizeToolbar(
|
||||
put(CropLock.LOCKED, cropLockImageVector)
|
||||
put(CropLock.UNLOCKED, cropUnlockImageVector)
|
||||
}
|
||||
},
|
||||
targetToContentDescriptionMap = remember(cropLockContentDescription, cropUnlockContentDescription) {
|
||||
EnumMap<CropLock, String>(
|
||||
CropLock::class.java
|
||||
).apply {
|
||||
put(CropLock.LOCKED, cropLockContentDescription)
|
||||
put(CropLock.UNLOCKED, cropUnlockContentDescription)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -231,7 +243,8 @@ private fun DiscardButton(imageEditorController: ImageController) {
|
||||
private inline fun <reified E : Enum<E>> IconCrossfadeToggleButton(
|
||||
target: E,
|
||||
crossinline setTarget: (E) -> Unit,
|
||||
targetToImageMap: EnumMap<E, ImageVector>
|
||||
targetToImageMap: EnumMap<E, ImageVector>,
|
||||
targetToContentDescriptionMap: EnumMap<E, String>
|
||||
) {
|
||||
IconButtons.IconButton(
|
||||
onClick = { setTarget(target.next()) }
|
||||
@@ -239,7 +252,7 @@ private inline fun <reified E : Enum<E>> IconCrossfadeToggleButton(
|
||||
Crossfade(target) { enumValue ->
|
||||
Icon(
|
||||
imageVector = targetToImageMap[enumValue]!!,
|
||||
contentDescription = null, // TODO
|
||||
contentDescription = targetToContentDescriptionMap[enumValue],
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -89,6 +89,9 @@
|
||||
<item quantity="other">%1$d items</item>
|
||||
</plurals>
|
||||
|
||||
<!-- Content description for the button that attaches a link to the text story being created -->
|
||||
<string name="TextStoryBar__add_link">Add link</string>
|
||||
|
||||
<!-- Name shown on the document preview when we cannot read the file\'s name. -->
|
||||
<string name="DocumentPage__unnamed_file">Unnamed file</string>
|
||||
|
||||
@@ -97,6 +100,11 @@
|
||||
<!-- Content description for button to enable brush mode -->
|
||||
<string name="DrawModeColorBar__brush">Brush</string>
|
||||
|
||||
<!-- Content description for the crop toggle when the crop area is currently constrained to a fixed aspect ratio. Tapping it removes the constraint. -->
|
||||
<string name="ImageEditorToolbar__aspect_ratio_locked">Aspect ratio locked</string>
|
||||
<!-- Content description for the crop toggle when the crop area can currently be resized freely. Tapping it constrains the crop area to a fixed aspect ratio. -->
|
||||
<string name="ImageEditorToolbar__aspect_ratio_unlocked">Aspect ratio unlocked</string>
|
||||
|
||||
<!-- Label for the toggle that blurs every face in the image being edited -->
|
||||
<string name="BlurFacesBar__blur_faces">Blur faces</string>
|
||||
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.mediasend.screens.capture
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.signal.core.ui.CoreUiDependenciesRule
|
||||
import org.signal.core.ui.compose.theme.SignalTheme
|
||||
|
||||
/**
|
||||
* The add link affordance is an icon-only button, so TalkBack has nothing but its content description to go on.
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(application = Application::class)
|
||||
class TextStoryBarTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
@get:Rule
|
||||
val coreUiDependenciesRule = CoreUiDependenciesRule(ApplicationProvider.getApplicationContext())
|
||||
|
||||
private val events = mutableListOf<MediaCaptureScreenEvents>()
|
||||
|
||||
@Test
|
||||
fun `Given the text story bar, when the labelled add link button is tapped, then a link is requested`() {
|
||||
composeTestRule.setContent {
|
||||
SignalTheme {
|
||||
TextStoryHorizontalBar(
|
||||
background = Brush.linearGradient(listOf(Color.Red, Color.Green)),
|
||||
onEvent = { events += it }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithContentDescription("Add link").performClick()
|
||||
|
||||
assertEquals(MediaCaptureScreenEvents.AddLinkToTextStory, events.single())
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.mediasend.screens.edit.image
|
||||
|
||||
import android.app.Application
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import io.mockk.mockk
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.signal.core.ui.CoreUiDependenciesRule
|
||||
import org.signal.core.ui.compose.theme.SignalTheme
|
||||
import org.signal.imageeditor.core.model.EditorModel
|
||||
import org.signal.mediasend.EditorState
|
||||
import org.signal.mediasend.MediaSendDependenciesRule
|
||||
import org.signal.mediasend.MediaSendFlowState
|
||||
import org.signal.mediasend.screens.edit.ImageController
|
||||
|
||||
/**
|
||||
* The aspect ratio toggle is an icon-only button whose icon is the only thing that says whether the crop is locked, so the
|
||||
* content description has to carry that state for TalkBack.
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(application = Application::class)
|
||||
class ImageEditorCropToolbarTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
@get:Rule
|
||||
val coreUiDependenciesRule = CoreUiDependenciesRule(ApplicationProvider.getApplicationContext())
|
||||
|
||||
@get:Rule
|
||||
val mediaSendDependenciesRule = MediaSendDependenciesRule(ApplicationProvider.getApplicationContext())
|
||||
|
||||
@Test
|
||||
fun `Given an unlocked crop, when rendering the toolbar, then the aspect ratio toggle announces that it is unlocked`() {
|
||||
setContent()
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(UNLOCKED).assertIsDisplayed()
|
||||
composeTestRule.onNodeWithContentDescription(LOCKED).assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given an unlocked crop, when the aspect ratio toggle is tapped, then it announces that it is locked`() {
|
||||
setContent()
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(UNLOCKED).performClick()
|
||||
composeTestRule.waitForIdle()
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(LOCKED).assertIsDisplayed()
|
||||
}
|
||||
|
||||
private fun setContent() {
|
||||
composeTestRule.setContent {
|
||||
SignalTheme {
|
||||
val controller = remember {
|
||||
ImageController(EDITOR_MODEL).apply { enterCropMode() }
|
||||
}
|
||||
|
||||
ImageEditorToolbar(
|
||||
imageEditorController = controller,
|
||||
state = MediaSendFlowState(),
|
||||
editorState = EditorState.Image(EDITOR_MODEL),
|
||||
onEvent = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val LOCKED = "Aspect ratio locked"
|
||||
private const val UNLOCKED = "Aspect ratio unlocked"
|
||||
|
||||
/** The editor model is never read by the toolbar, and a real one cannot be built under Robolectric's legacy graphics. */
|
||||
private val EDITOR_MODEL: EditorModel = mockk(relaxed = true)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user