Code cleanup, accessibility checks

This commit is contained in:
Jenna Vassar
2026-05-05 17:27:36 -07:00
parent aed149731b
commit 880a189617
7 changed files with 38 additions and 7 deletions
@@ -111,7 +111,7 @@ private fun RoomListModalBottomSheetContent(
ListItem(
headlineContent = {
Text(
text = stringResource(id = R.string.screen_roomlist_mark_as_read),
text = stringResource(id = CommonStrings.action_mark_as_read),
style = MaterialTheme.typography.bodyLarge,
)
},
@@ -36,7 +36,7 @@ class RoomListContextMenuTest {
contextMenu = contextMenu,
eventSink = eventsRecorder,
)
clickOn(R.string.screen_roomlist_mark_as_read)
clickOn(CommonStrings.action_mark_as_read)
eventsRecorder.assertList(
listOf(
RoomListEvent.HideContextMenu,
@@ -287,6 +287,11 @@ class TimelinePresenter(
// read marker advances in place — the SDK swaps the marker virtual item to a new position
// without changing the list length, e.g. when [markRoomAsFullyRead] is sent while at the
// bottom of the room.
//
// Limitation: when the read marker is outside the loaded window (gaps, pagination), this
// returns null and the jump-to-unread button stays hidden. Proper fix needs an SDK
// accessor for the m.fully_read marker plus FocusedOnEvent navigation on click; gated
// behind FeatureFlags.JumpToUnread until that lands.
val readMarkerIndex = remember { mutableStateOf<Int?>(null) }
LaunchedEffect(timelineItems, displayJumpToUnread) {
if (!displayJumpToUnread) {
@@ -48,7 +48,6 @@ import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
@@ -88,6 +87,7 @@ import io.element.android.libraries.designsystem.utils.animateScrollToItemCenter
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.testtags.TestTag
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.ui.strings.CommonStrings
@@ -418,6 +418,7 @@ private fun BoxScope.TimelineScrollHelper(
hasUnread = true,
onClick = ::jumpToReadMarker,
onMarkAsRead = onMarkAllAsRead,
testTag = TestTags.jumpToUnreadButton,
)
JumpToPositionButton(
icon = CompoundIcons.ChevronDown(),
@@ -426,6 +427,7 @@ private fun BoxScope.TimelineScrollHelper(
hasUnread = displayJumpToUnread && newEventState is NewEventState.FromOther,
onClick = ::jumpToBottom,
onMarkAsRead = onMarkAllAsRead,
testTag = TestTags.jumpToBottomButton,
dotAlignment = Alignment.BottomCenter,
)
}
@@ -439,6 +441,7 @@ private fun JumpToPositionButton(
hasUnread: Boolean,
onClick: () -> Unit,
onMarkAsRead: () -> Unit,
testTag: TestTag,
modifier: Modifier = Modifier,
dotAlignment: Alignment = Alignment.TopCenter,
) {
@@ -453,15 +456,15 @@ private fun JumpToPositionButton(
Box(
modifier = Modifier
.size(36.dp)
.shadow(elevation = 0.dp, shape = CircleShape)
.background(color = ElementTheme.colors.bgCanvasDefault, shape = CircleShape)
.clip(CircleShape)
.border(1.dp, ElementTheme.colors.borderDisabled, CircleShape)
.combinedClickable(
onClick = onClick,
onLongClick = { menuExpanded = true },
onLongClickLabel = stringResource(CommonStrings.action_open_context_menu),
)
.testTag(TestTags.floatingActionButton),
.testTag(testTag),
contentAlignment = Alignment.Center,
) {
Icon(
@@ -476,6 +479,8 @@ private fun JumpToPositionButton(
minWidth = 0.dp,
offset = DpOffset(x = -44.dp, y = 40.dp)
) {
// Hand-rolled instead of DropdownMenuItem: padding here is tighter
// than DropdownMenuItem's 12.dp default to match the Figma spec.
Row(
modifier = Modifier
.clickable {
@@ -1238,6 +1238,22 @@ class TimelinePresenterTest {
}
}
@Test
fun `present - MarkAllAsRead does not invoke markAsFullyRead when there is no latest event`() = runTest {
val markAsFullyReadRecorder = lambdaRecorder<RoomId, EventId, Unit> { _, _ -> }
val presenter = createTimelinePresenter(
timeline = FakeTimeline(getLatestEventIdResult = { Result.success(null) }),
markAsFullyRead = FakeMarkAsFullyRead(markAsFullyReadRecorder),
)
presenter.test {
val initialState = awaitFirstItem()
initialState.eventSink(TimelineEvent.MarkAllAsRead)
advanceUntilIdle()
markAsFullyReadRecorder.assertions().isNeverCalled()
cancelAndIgnoreRemainingEvents()
}
}
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
return awaitItem()
}
@@ -35,7 +35,6 @@ fun DropdownMenuItem(
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
enabled: Boolean = true,
contentPadding: PaddingValues = DropDownMenuItemDefaults.contentPadding,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
androidx.compose.material3.DropdownMenuItem(
@@ -50,7 +49,7 @@ fun DropdownMenuItem(
trailingIcon = trailingIcon,
enabled = enabled,
colors = DropDownMenuItemDefaults.colors(),
contentPadding = contentPadding,
contentPadding = DropDownMenuItemDefaults.contentPadding,
interactionSource = interactionSource
)
}
@@ -98,6 +98,12 @@ object TestTags {
*/
val floatingActionButton = TestTag("floating-action-button")
/**
* Timeline jump-to-position buttons (long-press exposes "Mark as read").
*/
val jumpToUnreadButton = TestTag("jump-to-unread-button")
val jumpToBottomButton = TestTag("jump-to-bottom-button")
/**
* Timeline.
*/