diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusPresenter.kt index 6079ae04cc..60f02ad05a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusPresenter.kt @@ -79,6 +79,8 @@ class UserStatusPresenter( } } + // In CustomInput mode, override the picker state's `emojiPickerSheetState` with the freshly composed emoji-picker + // sub-state so recompositions of the picker propagate up. Other modes pass through unchanged. val effectivePickerState = when (val current = pickerState) { is UserStatusPickerState.CustomInput -> { val emojiPickerState = presentEmojiPickerSheetState(isEmojiPickerVisible) diff --git a/libraries/emoji/impl/src/main/kotlin/io/element/android/libraries/emoji/impl/picker/DefaultEmojiPickerPresenter.kt b/libraries/emoji/impl/src/main/kotlin/io/element/android/libraries/emoji/impl/picker/DefaultEmojiPickerPresenter.kt index 6a0c4924b3..9d7dbfdce8 100644 --- a/libraries/emoji/impl/src/main/kotlin/io/element/android/libraries/emoji/impl/picker/DefaultEmojiPickerPresenter.kt +++ b/libraries/emoji/impl/src/main/kotlin/io/element/android/libraries/emoji/impl/picker/DefaultEmojiPickerPresenter.kt @@ -93,6 +93,8 @@ class DefaultEmojiPickerPresenter( val searchQuery = queryState.text.toString() LaunchedEffect(searchQuery, data) { if (searchQuery.isEmpty() || data.allEmojis.isEmpty()) { + // `SearchBarResultState.Initial` has no `equals` override, so reassigning a new instance + // would trigger a redundant emission each time this effect re-runs while the query is empty. if (emojiResults !is SearchBarResultState.Initial) { emojiResults = SearchBarResultState.Initial() } diff --git a/libraries/emoji/impl/src/main/kotlin/io/element/android/libraries/emoji/impl/picker/EmojiItem.kt b/libraries/emoji/impl/src/main/kotlin/io/element/android/libraries/emoji/impl/picker/EmojiItem.kt index 02ed46c0ae..930b42c789 100644 --- a/libraries/emoji/impl/src/main/kotlin/io/element/android/libraries/emoji/impl/picker/EmojiItem.kt +++ b/libraries/emoji/impl/src/main/kotlin/io/element/android/libraries/emoji/impl/picker/EmojiItem.kt @@ -91,6 +91,8 @@ internal fun EmojiItem( .onPlaced { coordinates -> val position = coordinates.positionInParent() val parentBounds = coordinates.parentLayoutCoordinates?.size ?: return@onPlaced + // Calculate the offset of the item inside its parent as a percentage, to be able to set the initial offset + // for the selection inside the skin tone picker relative to its size. itemOffsetPercent = if (parentBounds.width > 0) { position.x / parentBounds.width } else { @@ -102,6 +104,7 @@ internal fun EmojiItem( .background(backgroundColor, CircleShape) .indication(interactionSource, ripple()) .pointerInput(item) { + // Always detect long press and tap gestures detectTapGestures( onPress = { pressOffset -> val press = PressInteraction.Press(pressOffset) @@ -119,6 +122,7 @@ internal fun EmojiItem( ) } .then( + // Only detect drag after long press for those items which have a skin tone picker if (hasSkinTones) { Modifier.pointerInput(item) { val slotWidthPx = with(density) { SkinToneSlotSize.toPx() } @@ -135,6 +139,8 @@ internal fun EmojiItem( onDrag = { change, _ -> if (!dismissed) { change.consume() + // 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, + // 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 { @@ -146,11 +152,15 @@ internal fun EmojiItem( onDismissSkinPicker() } else { val skinItemsCount = item.skins!!.size + // Original + variants val totalSlots = 1 + skinItemsCount + // Calculate the whole size of the skin tone picker val pickerWidthPx = 2 * paddingPx + totalSlots * slotWidthPx + (totalSlots - 1) * spacingPx + // Calculate the initial offset inside the picker, given the relative position of the item inside its parent val initialOffset = pickerWidthPx * itemOffsetPercent val xInPicker = initialOffset + change.position.x + // If it's a valid offset, calculate the hovered index, otherwise, set it to -1 to indicate that no item is hovered val index = if (xInPicker in 0f..pickerWidthPx) { (xInPicker / slotWidthPx).toInt().coerceIn(0, totalSlots - 1) } else {