Skip to content

Commit 74a86c8

Browse files
Merge pull request #611 from android/jv/crane_calendar_a11y
[Crane] Add semantics for Calendar
2 parents 0a6ca1d + 584b9d6 commit 74a86c8

File tree

3 files changed

+64
-32
lines changed

3 files changed

+64
-32
lines changed

Crane/app/src/androidTest/java/androidx/compose/samples/crane/calendar/CalendarTest.kt

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,16 @@ import androidx.compose.samples.crane.data.DatesRepository
2727
import androidx.compose.samples.crane.ui.CraneTheme
2828
import androidx.compose.ui.test.ExperimentalTestApi
2929
import androidx.compose.ui.test.SemanticsMatcher
30-
import androidx.compose.ui.test.assertContentDescriptionEquals
3130
import androidx.compose.ui.test.assertIsDisplayed
31+
import androidx.compose.ui.test.assertTextEquals
3232
import androidx.compose.ui.test.hasScrollToKeyAction
3333
import androidx.compose.ui.test.junit4.ComposeTestRule
3434
import androidx.compose.ui.test.junit4.createAndroidComposeRule
35-
import androidx.compose.ui.test.onNodeWithContentDescription
35+
import androidx.compose.ui.test.onNodeWithText
36+
import androidx.compose.ui.test.onRoot
3637
import androidx.compose.ui.test.performClick
3738
import androidx.compose.ui.test.performScrollToKey
39+
import androidx.compose.ui.test.printToLog
3840
import dagger.hilt.android.testing.HiltAndroidRule
3941
import dagger.hilt.android.testing.HiltAndroidTest
4042
import org.junit.Before
@@ -70,45 +72,46 @@ class CalendarTest {
7072
@ExperimentalTestApi
7173
@Test
7274
fun scrollsToTheBottom() {
73-
composeTestRule.onNodeWithContentDescription("January 1").assertIsDisplayed()
75+
composeTestRule.onNodeWithText("January 1 2020").assertIsDisplayed()
7476
composeTestRule.onNode(hasScrollToKeyAction()).performScrollToKey("2020/12/4")
75-
composeTestRule.onNodeWithContentDescription("December 31").performClick()
77+
composeTestRule.onNodeWithText("December 31 2020").performClick()
7678
assert(datesRepository.datesSelected.toString() == "Dec 31")
7779
}
7880

7981
@Test
8082
fun onDaySelected() {
81-
composeTestRule.onNodeWithContentDescription("January 1").assertIsDisplayed()
82-
composeTestRule.onNodeWithContentDescription("January 2")
83+
composeTestRule.onNodeWithText("January 1 2020").assertIsDisplayed()
84+
composeTestRule.onNodeWithText("January 2 2020")
8385
.assertIsDisplayed().performClick()
84-
composeTestRule.onNodeWithContentDescription("January 3").assertIsDisplayed()
86+
composeTestRule.onNodeWithText("January 3 2020").assertIsDisplayed()
8587

8688
val datesNoSelected = composeTestRule.onDateNodes(NoSelected)
87-
datesNoSelected[0].assertContentDescriptionEquals("January 1")
88-
datesNoSelected[1].assertContentDescriptionEquals("January 3")
89+
datesNoSelected[0].assertTextEquals("January 1 2020")
90+
datesNoSelected[1].assertTextEquals("January 3 2020")
8991

90-
composeTestRule.onDateNode(FirstLastDay).assertContentDescriptionEquals("January 2")
92+
composeTestRule.onDateNode(FirstLastDay).assertTextEquals("January 2 2020")
9193
}
9294

9395
@Test
9496
fun twoDaysSelected() {
95-
composeTestRule.onNodeWithContentDescription("January 2")
97+
composeTestRule.onNodeWithText("January 2 2020")
9698
.assertIsDisplayed().performClick()
9799

98100
val datesNoSelectedOneClick = composeTestRule.onDateNodes(NoSelected)
99-
datesNoSelectedOneClick[0].assertContentDescriptionEquals("January 1")
100-
datesNoSelectedOneClick[1].assertContentDescriptionEquals("January 3")
101+
composeTestRule.onRoot().printToLog("JOLO")
102+
datesNoSelectedOneClick[0].assertTextEquals("January 1 2020")
103+
datesNoSelectedOneClick[1].assertTextEquals("January 3 2020")
101104

102-
composeTestRule.onNodeWithContentDescription("January 4")
105+
composeTestRule.onNodeWithText("January 4 2020")
103106
.assertIsDisplayed().performClick()
104107

105-
composeTestRule.onDateNode(FirstDay).assertContentDescriptionEquals("January 2")
106-
composeTestRule.onDateNode(Selected).assertContentDescriptionEquals("January 3")
107-
composeTestRule.onDateNode(LastDay).assertContentDescriptionEquals("January 4")
108+
composeTestRule.onDateNode(FirstDay).assertTextEquals("January 2 2020")
109+
composeTestRule.onDateNode(Selected).assertTextEquals("January 3 2020")
110+
composeTestRule.onDateNode(LastDay).assertTextEquals("January 4 2020")
108111

109112
val datesNoSelected = composeTestRule.onDateNodes(NoSelected)
110-
datesNoSelected[0].assertContentDescriptionEquals("January 1")
111-
datesNoSelected[1].assertContentDescriptionEquals("January 5")
113+
datesNoSelected[0].assertTextEquals("January 1 2020")
114+
datesNoSelected[1].assertTextEquals("January 5 2020")
112115
}
113116
}
114117

Crane/app/src/main/java/androidx/compose/samples/crane/calendar/Calendar.kt

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import androidx.compose.material.MaterialTheme
3636
import androidx.compose.material.Surface
3737
import androidx.compose.material.Text
3838
import androidx.compose.runtime.Composable
39+
import androidx.compose.samples.crane.R
3940
import androidx.compose.samples.crane.calendar.model.CalendarDay
4041
import androidx.compose.samples.crane.calendar.model.CalendarMonth
4142
import androidx.compose.samples.crane.calendar.model.DayOfWeek
@@ -48,10 +49,14 @@ import androidx.compose.samples.crane.util.SemiRect
4849
import androidx.compose.ui.Alignment
4950
import androidx.compose.ui.Modifier
5051
import androidx.compose.ui.graphics.Color
52+
import androidx.compose.ui.res.stringResource
5153
import androidx.compose.ui.semantics.SemanticsPropertyKey
5254
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
53-
import androidx.compose.ui.semantics.contentDescription
55+
import androidx.compose.ui.semantics.clearAndSetSemantics
5456
import androidx.compose.ui.semantics.semantics
57+
import androidx.compose.ui.semantics.stateDescription
58+
import androidx.compose.ui.semantics.text
59+
import androidx.compose.ui.text.AnnotatedString
5560
import androidx.compose.ui.tooling.preview.Preview
5661
import androidx.compose.ui.unit.dp
5762
import com.google.accompanist.insets.navigationBarsHeight
@@ -78,7 +83,7 @@ fun Calendar(
7883

7984
@Composable
8085
private fun MonthHeader(modifier: Modifier = Modifier, month: String, year: String) {
81-
Row(modifier = modifier) {
86+
Row(modifier = modifier.clearAndSetSemantics { }) {
8287
Text(
8388
modifier = Modifier.weight(1f),
8489
text = month,
@@ -112,10 +117,7 @@ private fun Week(
112117
Day(
113118
day,
114119
onDayClicked,
115-
Modifier.semantics {
116-
contentDescription = "${month.name} ${day.value}"
117-
dayStatusProperty = day.status
118-
}
120+
month
119121
)
120122
}
121123
Surface(modifier = spaceModifiers, color = rightFillColor) {
@@ -126,7 +128,7 @@ private fun Week(
126128

127129
@Composable
128130
private fun DaysOfWeek(modifier: Modifier = Modifier) {
129-
Row(modifier = modifier) {
131+
Row(modifier = modifier.clearAndSetSemantics { }) {
130132
for (day in DayOfWeek.values()) {
131133
Day(name = day.name.take(1))
132134
}
@@ -137,20 +139,28 @@ private fun DaysOfWeek(modifier: Modifier = Modifier) {
137139
private fun Day(
138140
day: CalendarDay,
139141
onDayClicked: (CalendarDay) -> Unit,
142+
month: CalendarMonth,
140143
modifier: Modifier = Modifier
141144
) {
142145
val enabled = day.status != DaySelectedStatus.NonClickable
143146
DayContainer(
144-
modifier = modifier,
145-
onClick = { if (day.status != DaySelectedStatus.NonClickable) onDayClicked(day) },
147+
modifier = modifier.semantics {
148+
if (enabled) text = AnnotatedString("${month.name} ${day.value} ${month.year}")
149+
dayStatusProperty = day.status
150+
},
151+
selected = day.status != DaySelectedStatus.NoSelected,
152+
onClick = { onDayClicked(day) },
146153
onClickEnabled = enabled,
147-
backgroundColor = day.status.color(MaterialTheme.colors)
154+
backgroundColor = day.status.color(MaterialTheme.colors),
155+
onClickLabel = stringResource(id = R.string.click_label_select)
148156
) {
149157
DayStatusContainer(status = day.status) {
150158
Text(
151159
modifier = Modifier
152160
.fillMaxSize()
153-
.wrapContentSize(Alignment.Center),
161+
.wrapContentSize(Alignment.Center)
162+
// Parent will handle semantics
163+
.clearAndSetSemantics {},
154164
text = day.value,
155165
style = MaterialTheme.typography.body1.copy(color = Color.White)
156166
)
@@ -173,17 +183,33 @@ private fun Day(name: String) {
173183
@Composable
174184
private fun DayContainer(
175185
modifier: Modifier = Modifier,
186+
selected: Boolean = false,
176187
onClick: () -> Unit = { },
177188
onClickEnabled: Boolean = true,
178189
backgroundColor: Color = Color.Transparent,
190+
onClickLabel: String? = null,
179191
content: @Composable () -> Unit
180192
) {
181193
// What if this doesn't fit the screen? - LayoutFlexible(1f) + LayoutAspectRatio(1f)
194+
val stateDescriptionLabel = stringResource(
195+
if (selected) R.string.state_descr_selected else R.string.state_descr_not_selected
196+
)
182197
Surface(
183-
modifier = modifier.size(width = CELL_SIZE, height = CELL_SIZE),
198+
modifier = modifier
199+
.size(width = CELL_SIZE, height = CELL_SIZE)
200+
.then(
201+
if (onClickEnabled) {
202+
modifier.semantics {
203+
stateDescription = stateDescriptionLabel
204+
}
205+
} else {
206+
modifier.clearAndSetSemantics { }
207+
}
208+
),
184209
onClick = onClick,
185210
enabled = onClickEnabled,
186-
color = backgroundColor
211+
color = backgroundColor,
212+
onClickLabel = onClickLabel
187213
) {
188214
content()
189215
}

Crane/app/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,7 @@
2121
<string name="cd_back">Back</string>
2222
<string name="cd_loading">Loading</string>
2323
<string name="cd_drawer">Open drawer</string>
24+
<string name="click_label_select">select</string>
25+
<string name="state_descr_selected">Selected</string>
26+
<string name="state_descr_not_selected">Not selected</string>
2427
</resources>

0 commit comments

Comments
 (0)