Skip to content

Commit d988ae3

Browse files
committed
[a11y] Let keyboard shortcut Shift + F10 trigger the same action than a long click
1 parent 27ad621 commit d988ae3

File tree

15 files changed

+104
-30
lines changed

15 files changed

+104
-30
lines changed

features/home/impl/src/main/kotlin/io/element/android/features/home/impl/components/RoomSummaryRow.kt

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import io.element.android.libraries.core.extensions.orEmpty
4646
import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAtom
4747
import io.element.android.libraries.designsystem.components.avatar.Avatar
4848
import io.element.android.libraries.designsystem.components.avatar.AvatarType
49+
import io.element.android.libraries.designsystem.modifiers.onShiftF10
4950
import io.element.android.libraries.designsystem.preview.ElementPreview
5051
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
5152
import io.element.android.libraries.designsystem.theme.components.Button
@@ -170,14 +171,15 @@ private fun RoomSummaryScaffoldRow(
170171
hideAvatarImage: Boolean = false,
171172
content: @Composable ColumnScope.() -> Unit
172173
) {
173-
val clickModifier = Modifier.combinedClickable(
174-
onClick = { onClick(room) },
175-
onLongClick = { onLongClick(room) },
176-
onLongClickLabel = stringResource(CommonStrings.action_open_context_menu),
177-
indication = ripple(),
178-
interactionSource = remember { MutableInteractionSource() }
179-
)
180-
174+
val clickModifier = Modifier
175+
.combinedClickable(
176+
onClick = { onClick(room) },
177+
onLongClick = { onLongClick(room) },
178+
onLongClickLabel = stringResource(CommonStrings.action_open_context_menu),
179+
indication = ripple(),
180+
interactionSource = remember { MutableInteractionSource() }
181+
)
182+
.onShiftF10 { onLongClick(room) }
181183
Row(
182184
modifier = modifier
183185
.fillMaxWidth()

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageEventBubble.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import io.element.android.features.messages.impl.timeline.model.bubble.BubbleSta
3838
import io.element.android.features.messages.impl.timeline.model.bubble.BubbleStateProvider
3939
import io.element.android.libraries.core.extensions.to01
4040
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
41+
import io.element.android.libraries.designsystem.modifiers.onShiftF10
4142
import io.element.android.libraries.designsystem.preview.ElementPreview
4243
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
4344
import io.element.android.libraries.designsystem.text.toDp
@@ -96,12 +97,14 @@ fun MessageEventBubble(
9697
val clickableModifier = if (isTalkbackActive()) {
9798
Modifier
9899
} else {
99-
Modifier.combinedClickable(
100-
onClick = onClick,
101-
onLongClick = onLongClick,
102-
indication = ripple(),
103-
interactionSource = interactionSource
104-
)
100+
Modifier
101+
.combinedClickable(
102+
onClick = onClick,
103+
onLongClick = onLongClick,
104+
indication = ripple(),
105+
interactionSource = interactionSource
106+
)
107+
.onShiftF10(onLongClick)
105108
}
106109

107110
// Ignore state.isHighlighted for now, we need a design decision on it.

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageStateEventContainer.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import androidx.compose.ui.draw.clip
1919
import androidx.compose.ui.graphics.Color
2020
import androidx.compose.ui.res.stringResource
2121
import androidx.compose.ui.unit.dp
22+
import io.element.android.libraries.designsystem.modifiers.onShiftF10
2223
import io.element.android.libraries.designsystem.preview.ElementPreview
2324
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
2425
import io.element.android.libraries.designsystem.theme.components.Surface
@@ -46,7 +47,8 @@ fun MessageStateEventContainer(
4647
onLongClickLabel = stringResource(CommonStrings.action_open_context_menu),
4748
indication = ripple(),
4849
interactionSource = interactionSource
49-
),
50+
)
51+
.onShiftF10(onLongClick),
5052
color = backgroundColor,
5153
shape = shape,
5254
content = content

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessagesReactionButton.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import io.element.android.features.messages.impl.timeline.model.AggregatedReacti
4343
import io.element.android.features.messages.impl.timeline.model.AggregatedReactionProvider
4444
import io.element.android.features.messages.impl.timeline.model.aTimelineItemReactions
4545
import io.element.android.libraries.designsystem.icons.CompoundDrawables
46+
import io.element.android.libraries.designsystem.modifiers.onShiftF10
4647
import io.element.android.libraries.designsystem.preview.ElementPreview
4748
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
4849
import io.element.android.libraries.designsystem.text.toDp
@@ -107,6 +108,7 @@ fun MessagesReactionButton(
107108
onLongClickLabel = stringResource(CommonStrings.action_open_context_menu),
108109
onLongClick = onLongClick
109110
)
111+
.onShiftF10(onLongClick)
110112
// Inner border, to highlight when selected
111113
.border(BorderStroke(1.dp, borderColor), RoundedCornerShape(corner = CornerSize(12.dp)))
112114
.background(buttonColor, RoundedCornerShape(corner = CornerSize(12.dp)))

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemCallNotifyView.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import io.element.android.features.roomcall.api.RoomCallState
3434
import io.element.android.features.roomcall.api.RoomCallStateProvider
3535
import io.element.android.libraries.designsystem.components.avatar.Avatar
3636
import io.element.android.libraries.designsystem.components.avatar.AvatarType
37+
import io.element.android.libraries.designsystem.modifiers.onShiftF10
3738
import io.element.android.libraries.designsystem.preview.ElementPreview
3839
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
3940
import io.element.android.libraries.designsystem.text.toDp
@@ -57,6 +58,7 @@ internal fun TimelineItemCallNotifyView(
5758
onLongClick = { onLongClick(event) },
5859
onLongClickLabel = stringResource(CommonStrings.action_open_context_menu),
5960
)
61+
.onShiftF10 { onLongClick(event) }
6062
.padding(12.dp),
6163
horizontalArrangement = Arrangement.spacedBy(12.dp),
6264
verticalAlignment = Alignment.CenterVertically,

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
3737
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
3838
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent
3939
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
40+
import io.element.android.libraries.designsystem.modifiers.onShiftF10
4041
import io.element.android.libraries.designsystem.modifiers.subtleColorStops
4142
import io.element.android.libraries.designsystem.preview.ElementPreview
4243
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@@ -148,11 +149,13 @@ internal fun TimelineItemRow(
148149
// Custom clickable that applies over the whole item for accessibility
149150
.then(
150151
if (isTalkbackActive()) {
151-
Modifier.combinedClickable(
152-
onClick = { onContentClick(timelineItem) },
153-
onLongClick = { onLongClick(timelineItem) },
154-
onLongClickLabel = stringResource(CommonStrings.action_open_context_menu),
155-
)
152+
Modifier
153+
.combinedClickable(
154+
onClick = { onContentClick(timelineItem) },
155+
onLongClick = { onLongClick(timelineItem) },
156+
onLongClickLabel = stringResource(CommonStrings.action_open_context_menu),
157+
)
158+
.onShiftF10 { onLongClick(timelineItem) }
156159
} else {
157160
Modifier
158161
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
4848
import io.element.android.features.messages.impl.timeline.protection.ProtectedView
4949
import io.element.android.features.messages.impl.timeline.protection.coerceRatioWhenHidingContent
5050
import io.element.android.libraries.designsystem.components.blurhash.blurHashBackground
51+
import io.element.android.libraries.designsystem.modifiers.onShiftF10
5152
import io.element.android.libraries.designsystem.preview.ElementPreview
5253
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
5354
import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle
@@ -91,10 +92,12 @@ fun TimelineItemImageView(
9192
.then(if (isLoaded) Modifier.background(Color.White) else Modifier)
9293
.then(
9394
if (!isTalkbackActive() && onContentClick != null) {
94-
Modifier.combinedClickable(
95-
onClick = onContentClick,
96-
onLongClick = onLongClick
97-
)
95+
Modifier
96+
.combinedClickable(
97+
onClick = onContentClick,
98+
onLongClick = onLongClick,
99+
)
100+
.onShiftF10(onLongClick)
98101
} else {
99102
Modifier
100103
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
3131
import io.element.android.features.messages.impl.timeline.protection.ProtectedView
3232
import io.element.android.features.messages.impl.timeline.protection.coerceRatioWhenHidingContent
3333
import io.element.android.libraries.designsystem.components.blurhash.blurHashBackground
34+
import io.element.android.libraries.designsystem.modifiers.onShiftF10
3435
import io.element.android.libraries.designsystem.preview.ElementPreview
3536
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
3637
import io.element.android.libraries.matrix.ui.media.MediaRequestData
@@ -74,6 +75,7 @@ fun TimelineItemStickerView(
7475
onLongClick = onLongClick,
7576
onLongClickLabel = stringResource(CommonStrings.action_open_context_menu),
7677
)
78+
.onShiftF10(onLongClick)
7779
} else {
7880
Modifier
7981
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
5454
import io.element.android.features.messages.impl.timeline.protection.ProtectedView
5555
import io.element.android.features.messages.impl.timeline.protection.coerceRatioWhenHidingContent
5656
import io.element.android.libraries.designsystem.components.blurhash.blurHashBackground
57+
import io.element.android.libraries.designsystem.modifiers.onShiftF10
5758
import io.element.android.libraries.designsystem.modifiers.roundedBackground
5859
import io.element.android.libraries.designsystem.preview.ElementPreview
5960
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@@ -105,10 +106,12 @@ fun TimelineItemVideoView(
105106
.then(if (isLoaded) Modifier.background(Color.White) else Modifier)
106107
.then(
107108
if (!isTalkbackActive && onContentClick != null) {
108-
Modifier.combinedClickable(
109-
onClick = onContentClick,
110-
onLongClick = onLongClick
111-
)
109+
Modifier
110+
.combinedClickable(
111+
onClick = onContentClick,
112+
onLongClick = onLongClick,
113+
)
114+
.onShiftF10(onLongClick)
112115
} else {
113116
Modifier
114117
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.designsystem.modifiers
9+
10+
import androidx.compose.ui.Modifier
11+
import androidx.compose.ui.input.key.Key
12+
import androidx.compose.ui.input.key.KeyEventType
13+
import androidx.compose.ui.input.key.isShiftPressed
14+
import androidx.compose.ui.input.key.key
15+
import androidx.compose.ui.input.key.onKeyEvent
16+
import androidx.compose.ui.input.key.type
17+
18+
/**
19+
* Modifier to handle Shift + F10 key events.
20+
* This is typically used to trigger context menus in desktop applications.
21+
*
22+
* @param onShiftF10Press The callback to invoke when Shift + F10 is pressed.
23+
*/
24+
fun Modifier.onShiftF10(
25+
onShiftF10Press: (() -> Unit)?,
26+
): Modifier = then(
27+
if (onShiftF10Press == null) {
28+
Modifier
29+
} else {
30+
Modifier.onKeyEvent { keyEvent ->
31+
// invoke the callback when the user presses Shift + F10
32+
if (keyEvent.type == KeyEventType.KeyUp &&
33+
keyEvent.isShiftPressed &&
34+
keyEvent.key == Key.F10) {
35+
onShiftF10Press()
36+
true
37+
} else {
38+
false
39+
}
40+
}
41+
}
42+
)

0 commit comments

Comments
 (0)