mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 06:55:41 +00:00
Fix selection behaviour when long pressing emojis with no skin tone variants
Also improve the threshold calculation to dismiss the displayed skin tone picker a bit
This commit is contained in:
+37
-24
@@ -30,6 +30,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Path
|
import androidx.compose.ui.graphics.Path
|
||||||
import androidx.compose.ui.graphics.drawscope.Fill
|
import androidx.compose.ui.graphics.drawscope.Fill
|
||||||
@@ -48,7 +49,7 @@ import io.element.android.compound.theme.ElementTheme
|
|||||||
import io.element.android.emojibasebindings.Emoji
|
import io.element.android.emojibasebindings.Emoji
|
||||||
import io.element.android.features.messages.impl.timeline.a11y.a11yReactionAction
|
import io.element.android.features.messages.impl.timeline.a11y.a11yReactionAction
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.SkinTonePadding
|
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.SkinTonePadding
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.SkinTonePickerContent
|
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.SkinTonePicker
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.SkinToneSlotSize
|
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.SkinToneSlotSize
|
||||||
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.SkinToneSlotSpacing
|
import io.element.android.features.messages.impl.timeline.components.customreaction.picker.SkinToneSlotSpacing
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
@@ -83,6 +84,7 @@ fun EmojiItem(
|
|||||||
emoji = item.unicode,
|
emoji = item.unicode,
|
||||||
userAlreadyReacted = isSelected,
|
userAlreadyReacted = isSelected,
|
||||||
)
|
)
|
||||||
|
val hasSkinTones = !item.skins.isNullOrEmpty()
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.sizeIn(minWidth = 40.dp, minHeight = 40.dp)
|
.sizeIn(minWidth = 40.dp, minHeight = 40.dp)
|
||||||
@@ -90,33 +92,29 @@ fun EmojiItem(
|
|||||||
.background(backgroundColor, CircleShape)
|
.background(backgroundColor, CircleShape)
|
||||||
.indication(interactionSource, ripple())
|
.indication(interactionSource, ripple())
|
||||||
.pointerInput(item) {
|
.pointerInput(item) {
|
||||||
detectTapGestures(
|
// Only detect drag after long press for those items which have a skin tone picker
|
||||||
onPress = { pressOffset ->
|
if (hasSkinTones) {
|
||||||
val press = PressInteraction.Press(pressOffset)
|
var startOffset = Offset.Zero
|
||||||
interactionSource.emit(press)
|
|
||||||
if (tryAwaitRelease()) {
|
|
||||||
interactionSource.emit(PressInteraction.Release(press))
|
|
||||||
} else {
|
|
||||||
interactionSource.emit(PressInteraction.Cancel(press))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onTap = { onSelectEmoji(item) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.pointerInput(item) {
|
|
||||||
detectDragGesturesAfterLongPress(
|
detectDragGesturesAfterLongPress(
|
||||||
onDragStart = { _ ->
|
onDragStart = { position ->
|
||||||
if (item.skins != null && onLongPress != null) {
|
if (onLongPress != null) {
|
||||||
|
startOffset = position
|
||||||
dismissed = false
|
dismissed = false
|
||||||
onLongPress(item)
|
onLongPress(item)
|
||||||
hoveredIndex = -1
|
hoveredIndex = -1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDrag = { change, _ ->
|
onDrag = { change, _ ->
|
||||||
if (item.skins != null && !dismissed) {
|
if (!dismissed) {
|
||||||
change.consume()
|
change.consume()
|
||||||
val yThresholdPx = with(density) { 100.dp.toPx() }
|
// It's a valid drag event if it's within ~2 items height above, so, on top of the current one or the skin tone selector,
|
||||||
if (change.position.y > yThresholdPx) {
|
// or 1 item below, which should be far enough to not be triggered by mistake
|
||||||
|
val isValidDrag = if (change.position.y < startOffset.y) {
|
||||||
|
startOffset.y - change.position.y <= itemSize.height * 2f
|
||||||
|
} else {
|
||||||
|
change.position.y - startOffset.y <= itemSize.height
|
||||||
|
}
|
||||||
|
if (!isValidDrag) {
|
||||||
dismissed = true
|
dismissed = true
|
||||||
hoveredIndex = -1
|
hoveredIndex = -1
|
||||||
onDismissSkinPicker?.invoke()
|
onDismissSkinPicker?.invoke()
|
||||||
@@ -131,7 +129,7 @@ fun EmojiItem(
|
|||||||
val centerXPx = itemSize.width / 2f
|
val centerXPx = itemSize.width / 2f
|
||||||
val pickerLeftPx = centerXPx - pickerHalfWidthPx
|
val pickerLeftPx = centerXPx - pickerHalfWidthPx
|
||||||
val xInPicker = change.position.x - pickerLeftPx
|
val xInPicker = change.position.x - pickerLeftPx
|
||||||
val index = if (xInPicker >= 0f && xInPicker <= pickerWidthPx) {
|
val index = if (xInPicker in 0f..pickerWidthPx) {
|
||||||
(xInPicker / slotWidthPx).toInt().coerceIn(0, totalSlots - 1)
|
(xInPicker / slotWidthPx).toInt().coerceIn(0, totalSlots - 1)
|
||||||
} else {
|
} else {
|
||||||
-1
|
-1
|
||||||
@@ -164,10 +162,25 @@ fun EmojiItem(
|
|||||||
onDragCancel = {
|
onDragCancel = {
|
||||||
dismissed = false
|
dismissed = false
|
||||||
hoveredIndex = -1
|
hoveredIndex = -1
|
||||||
onDismissSkinPicker?.invoke()
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always detect long press and tap gestures
|
||||||
|
detectTapGestures(
|
||||||
|
onPress = { pressOffset ->
|
||||||
|
val press = PressInteraction.Press(pressOffset)
|
||||||
|
interactionSource.emit(press)
|
||||||
|
if (tryAwaitRelease()) {
|
||||||
|
interactionSource.emit(PressInteraction.Release(press))
|
||||||
|
onSelectEmoji(item)
|
||||||
|
} else {
|
||||||
|
interactionSource.emit(PressInteraction.Cancel(press))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTap = { onSelectEmoji(item) },
|
||||||
|
)
|
||||||
|
}
|
||||||
.clearAndSetSemantics {
|
.clearAndSetSemantics {
|
||||||
contentDescription = description
|
contentDescription = description
|
||||||
},
|
},
|
||||||
@@ -177,7 +190,7 @@ fun EmojiItem(
|
|||||||
text = item.unicode,
|
text = item.unicode,
|
||||||
style = LocalTextStyle.current.copy(fontSize = emojiSize),
|
style = LocalTextStyle.current.copy(fontSize = emojiSize),
|
||||||
)
|
)
|
||||||
if (item.skins != null) {
|
if (hasSkinTones) {
|
||||||
Canvas(
|
Canvas(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.BottomEnd)
|
.align(Alignment.BottomEnd)
|
||||||
@@ -201,7 +214,7 @@ fun EmojiItem(
|
|||||||
alignment = Alignment.BottomCenter,
|
alignment = Alignment.BottomCenter,
|
||||||
offset = IntOffset(0, popupOffsetPx),
|
offset = IntOffset(0, popupOffsetPx),
|
||||||
) {
|
) {
|
||||||
SkinTonePickerContent(
|
SkinTonePicker(
|
||||||
emoji = item,
|
emoji = item,
|
||||||
onSelect = { selectedEmoji ->
|
onSelect = { selectedEmoji ->
|
||||||
onSelectEmoji(selectedEmoji)
|
onSelectEmoji(selectedEmoji)
|
||||||
|
|||||||
Reference in New Issue
Block a user