Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## Unreleased

## Changed

- #1369 Add content descriptions to drag handles and custom "Move up"/"Move down" accessibility actions for trigger and action list items, improving TalkBack support for reordering.

## [4.1.1](https://github.com/sds100/KeyMapper/releases/tag/v4.1.1)

#### 15 May 2026
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
Expand All @@ -62,11 +65,16 @@ fun ActionListItem(
onRemoveClick: () -> Unit = {},
onFixClick: () -> Unit = {},
onTestClick: () -> Unit = {},
onMoveUp: (() -> Unit)? = null,
onMoveDown: (() -> Unit)? = null,
) {
val draggableState = rememberDraggableState {
dragDropState?.onDrag(Offset(0f, it))
}

val moveUpLabel = stringResource(R.string.accessibility_action_move_up)
val moveDownLabel = stringResource(R.string.accessibility_action_move_down)

Column(modifier = modifier.fillMaxWidth()) {
ElevatedCard(
modifier = Modifier
Expand All @@ -83,7 +91,19 @@ fun ActionListItem(
dragDropState?.onDragStart(index, offset)
},
onDragStopped = { dragDropState?.onDragInterrupted() },
),
)
.semantics {
if (isReorderingEnabled) {
customActions = buildList {
onMoveUp?.let { action ->
add(CustomAccessibilityAction(moveUpLabel) { action(); true })
}
onMoveDown?.let { action ->
add(CustomAccessibilityAction(moveDownLabel) { action(); true })
}
}
}
},
colors = CardDefaults.elevatedCardColors(
containerColor = if (isDragging) {
MaterialTheme.colorScheme.surfaceContainerHighest
Expand All @@ -102,7 +122,7 @@ fun ActionListItem(
Icon(
modifier = Modifier.size(24.dp),
imageVector = Icons.Rounded.DragHandle,
contentDescription = null,
contentDescription = stringResource(R.string.drag_handle_for, model.text),
tint = MaterialTheme.colorScheme.onSurface,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,16 @@ private fun ActionList(
onRemoveClick = { onRemoveClick(model.id) },
onFixClick = { onFixErrorClick(model.id) },
onTestClick = { onTestClick(model.id) },
onMoveUp = if (isReorderingEnabled && index > 0) {
{ onMove(index, index - 1) }
} else {
null
},
onMoveDown = if (isReorderingEnabled && index < actionList.size - 1) {
{ onMove(index, index + 1) }
} else {
null
},
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,16 @@ private fun TriggerList(
onEditClick = { onEditClick(model.id) },
onRemoveClick = { onRemoveClick(model.id) },
onFixClick = onFixErrorClick,
onMoveUp = if (isReorderingEnabled && index > 0) {
{ onMove(index, index - 1) }
} else {
null
},
onMoveDown = if (isReorderingEnabled && index < triggerList.size - 1) {
{ onMove(index, index + 1) }
} else {
null
},
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
Expand All @@ -60,11 +63,67 @@ fun TriggerKeyListItem(
onEditClick: () -> Unit = {},
onRemoveClick: () -> Unit = {},
onFixClick: (TriggerError) -> Unit = {},
onMoveUp: (() -> Unit)? = null,
onMoveDown: (() -> Unit)? = null,
) {
val draggableState = rememberDraggableState {
dragDropState?.onDrag(Offset(0f, it))
}

val primaryText = when (model) {
is TriggerKeyListItemModel.Assistant -> when (model.assistantType) {
AssistantTriggerType.ANY -> stringResource(
R.string.assistant_any_trigger_name,
)

AssistantTriggerType.VOICE -> stringResource(
R.string.assistant_voice_trigger_name,
)

AssistantTriggerType.DEVICE -> stringResource(
R.string.assistant_device_trigger_name,
)
}

is TriggerKeyListItemModel.FloatingButton -> if (model.buttonName.isBlank()) {
stringResource(R.string.trigger_key_floating_button_description_empty)
} else {
stringResource(
R.string.trigger_key_floating_button_description,
model.buttonName,
)
}

is TriggerKeyListItemModel.KeyEvent -> model.keyName

is TriggerKeyListItemModel.EvdevEvent -> model.keyName

is TriggerKeyListItemModel.FloatingButtonDeleted -> stringResource(
R.string.trigger_error_floating_button_deleted_title,
)

is TriggerKeyListItemModel.FingerprintGesture -> when (model.gestureType) {
FingerprintGestureType.SWIPE_UP -> stringResource(
R.string.trigger_key_fingerprint_gesture_up,
)

FingerprintGestureType.SWIPE_DOWN -> stringResource(
R.string.trigger_key_fingerprint_gesture_down,
)

FingerprintGestureType.SWIPE_LEFT -> stringResource(
R.string.trigger_key_fingerprint_gesture_left,
)

FingerprintGestureType.SWIPE_RIGHT -> stringResource(
R.string.trigger_key_fingerprint_gesture_right,
)
}
}

val moveUpLabel = stringResource(R.string.accessibility_action_move_up)
val moveDownLabel = stringResource(R.string.accessibility_action_move_down)

Column(modifier = modifier.fillMaxWidth()) {
ElevatedCard(
modifier = Modifier
Expand All @@ -81,7 +140,19 @@ fun TriggerKeyListItem(
dragDropState?.onDragStart(index, offset)
},
onDragStopped = { dragDropState?.onDragInterrupted() },
),
)
.semantics {
if (isReorderingEnabled) {
customActions = buildList {
onMoveUp?.let { action ->
add(CustomAccessibilityAction(moveUpLabel) { action(); true })
}
onMoveDown?.let { action ->
add(CustomAccessibilityAction(moveDownLabel) { action(); true })
}
}
}
},
colors = CardDefaults.elevatedCardColors(
containerColor = if (isDragging) {
MaterialTheme.colorScheme.surfaceContainerHighest
Expand All @@ -100,7 +171,7 @@ fun TriggerKeyListItem(
Icon(
modifier = Modifier.size(24.dp),
imageVector = Icons.Rounded.DragHandle,
contentDescription = null,
contentDescription = stringResource(R.string.drag_handle_for, primaryText),
tint = MaterialTheme.colorScheme.onSurface,
)
}
Expand All @@ -127,57 +198,6 @@ fun TriggerKeyListItem(
}
}

val primaryText = when (model) {
is TriggerKeyListItemModel.Assistant -> when (model.assistantType) {
AssistantTriggerType.ANY -> stringResource(
R.string.assistant_any_trigger_name,
)

AssistantTriggerType.VOICE -> stringResource(
R.string.assistant_voice_trigger_name,
)

AssistantTriggerType.DEVICE -> stringResource(
R.string.assistant_device_trigger_name,
)
}

is TriggerKeyListItemModel.FloatingButton -> if (model.buttonName.isBlank()) {
stringResource(R.string.trigger_key_floating_button_description_empty)
} else {
stringResource(
R.string.trigger_key_floating_button_description,
model.buttonName,
)
}

is TriggerKeyListItemModel.KeyEvent -> model.keyName

is TriggerKeyListItemModel.EvdevEvent -> model.keyName

is TriggerKeyListItemModel.FloatingButtonDeleted -> stringResource(
R.string.trigger_error_floating_button_deleted_title,
)

is TriggerKeyListItemModel.FingerprintGesture -> when (model.gestureType) {
FingerprintGestureType.SWIPE_UP -> stringResource(
R.string.trigger_key_fingerprint_gesture_up,
)

FingerprintGestureType.SWIPE_DOWN -> stringResource(
R.string.trigger_key_fingerprint_gesture_down,
)

FingerprintGestureType.SWIPE_LEFT -> stringResource(
R.string.trigger_key_fingerprint_gesture_left,
)

FingerprintGestureType.SWIPE_RIGHT -> stringResource(
R.string.trigger_key_fingerprint_gesture_right,
)
}
}

Spacer(Modifier.width(8.dp))

if (model.error == null) {
Expand Down
2 changes: 2 additions & 0 deletions base/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,8 @@
<string name="sorting_drag_and_drop_list_help">Drag the handles to adjust priorities. The item at the top is the most important. You must tap the item to enable sorting and toggle ascending/descending.</string>
<string name="sorting_drag_and_drop_list_help_example">Example: To sort by Actions (ascending) first and Triggers (descending) second: tap and drag Actions to the first position and set it to ascending, then tap and drag Triggers to the second position and set it to descending.</string>
<string name="drag_handle_for">Drag handle for %1$s</string>
<string name="accessibility_action_move_up">Move up</string>
<string name="accessibility_action_move_down">Move down</string>
<string name="show_example">Show example</string>

<string name="dialog_title_request_notification_permission">Turn on notifications</string>
Expand Down
Loading