Display only valid emojis in recent emoji list (#5612)

* Create `:libraries:recentemojis` and move `AddRecentEmoji` and `GetRecentEmojis` there

- Make sure `GetRecentEmojis` won't return duplicate or invalid emojis.
- `ActionListPresenter` now handles merging suggested and recent emojis, not `ActionListView`.
This commit is contained in:
Jorge Martin Espinosa 2025-10-30 16:27:51 +01:00 committed by GitHub
parent 7facc40771
commit 45b5783b23
24 changed files with 351 additions and 111 deletions

View file

@ -73,7 +73,6 @@ import io.element.android.libraries.matrix.api.core.toThreadId
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.recentemojis.AddRecentEmoji
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.RoomInfo
@ -87,6 +86,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransa
import io.element.android.libraries.matrix.ui.messages.reply.map
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.room.getDirectRoomMember
import io.element.android.libraries.recentemojis.api.AddRecentEmoji
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.analytics.api.AnalyticsService

View file

@ -43,10 +43,10 @@ import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.recentemojis.GetRecentEmojis
import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.recentemojis.api.GetRecentEmojis
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -87,6 +87,8 @@ class DefaultActionListPresenter(
private val comparator = TimelineItemActionComparator()
private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏")
@Composable
override fun present(): ActionListState {
val localCoroutineScope = rememberCoroutineScope()
@ -146,6 +148,7 @@ class DefaultActionListPresenter(
val displayEmojiReactions = usersEventPermissions.canSendReaction && timelineItem.content.canReact()
if (actions.isNotEmpty() || displayEmojiReactions || verifiedUserSendFailure != VerifiedUserSendFailure.None) {
val recentEmojis = getRecentEmojis().getOrNull()?.toImmutableList() ?: persistentListOf()
target.value = ActionListState.Target.Success(
event = timelineItem,
sentTimeFull = dateFormatter.format(
@ -156,7 +159,10 @@ class DefaultActionListPresenter(
displayEmojiReactions = displayEmojiReactions,
verifiedUserSendFailure = verifiedUserSendFailure,
actions = actions.toImmutableList(),
recentEmojis = getRecentEmojis().getOrNull()?.toImmutableList() ?: persistentListOf()
// Merge suggested and recent emojis, removing duplicates and returning at most 100
recentEmojis = (suggestedEmojis + recentEmojis).distinct()
.take(100)
.toImmutableList()
)
} else {
target.value = ActionListState.Target.None

View file

@ -27,6 +27,8 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏")
override val values: Sequence<ActionListState>
get() {
val reactionsState = aTimelineItemReactions(1, isHighlighted = true)
@ -42,7 +44,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = aTimelineItemActionList(),
recentEmojis = persistentListOf(),
recentEmojis = suggestedEmojis,
)
),
anActionListState(
@ -58,7 +60,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
actions = aTimelineItemActionList(
copyAction = TimelineItemAction.CopyCaption,
),
recentEmojis = persistentListOf(),
recentEmojis = suggestedEmojis,
)
),
anActionListState(
@ -73,7 +75,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
actions = aTimelineItemActionList(
copyAction = TimelineItemAction.CopyCaption,
),
recentEmojis = persistentListOf(),
recentEmojis = suggestedEmojis,
)
),
anActionListState(
@ -88,7 +90,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
actions = aTimelineItemActionList(
copyAction = null,
),
recentEmojis = persistentListOf(),
recentEmojis = suggestedEmojis,
)
),
anActionListState(
@ -103,7 +105,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
actions = aTimelineItemActionList(
copyAction = TimelineItemAction.CopyCaption,
),
recentEmojis = persistentListOf(),
recentEmojis = suggestedEmojis,
)
),
anActionListState(
@ -118,7 +120,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
actions = aTimelineItemActionList(
copyAction = null,
),
recentEmojis = persistentListOf(),
recentEmojis = suggestedEmojis,
)
),
anActionListState(
@ -131,7 +133,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = aTimelineItemActionList(),
recentEmojis = persistentListOf(),
recentEmojis = suggestedEmojis,
)
),
anActionListState(
@ -144,7 +146,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = false,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = aTimelineItemActionList(),
recentEmojis = persistentListOf(),
recentEmojis = suggestedEmojis,
),
),
anActionListState(
@ -157,7 +159,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = false,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = aTimelineItemPollActionList(),
recentEmojis = persistentListOf(),
recentEmojis = suggestedEmojis,
),
),
anActionListState(
@ -170,7 +172,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = aTimelineItemActionList(),
recentEmojis = persistentListOf(),
recentEmojis = suggestedEmojis,
)
),
anActionListState(
@ -180,7 +182,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = true,
verifiedUserSendFailure = anUnsignedDeviceSendFailure(),
actions = aTimelineItemActionList(),
recentEmojis = persistentListOf(),
recentEmojis = suggestedEmojis,
)
),
)

View file

@ -97,8 +97,6 @@ import io.element.android.libraries.matrix.ui.messages.sender.SenderName
import io.element.android.libraries.matrix.ui.messages.sender.SenderNameMode
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -345,7 +343,6 @@ private fun MessageSummary(
}
private val emojiRippleRadius = 24.dp
private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏")
@Composable
private fun EmojiReactionsRow(
@ -360,12 +357,6 @@ private fun EmojiReactionsRow(
) {
val backgroundColor = ElementTheme.colors.bgCanvasDefault
val emojis = remember(recentEmojis) {
(suggestedEmojis + recentEmojis.filter { it !in suggestedEmojis })
.take(100)
.toImmutableList()
}
LazyRow(
modifier = Modifier
.weight(1f, fill = true)
@ -388,7 +379,7 @@ private fun EmojiReactionsRow(
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
items(emojis) { emoji ->
items(recentEmojis) { emoji ->
val isHighlighted = highlightedEmojis.contains(emoji)
EmojiButton(
modifier = Modifier

View file

@ -17,10 +17,10 @@ import androidx.compose.runtime.setValue
import dev.zacsweers.metro.Inject
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.recentemojis.GetRecentEmojis
import io.element.android.libraries.recentemojis.api.EmojibaseProvider
import io.element.android.libraries.recentemojis.api.GetRecentEmojis
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.launch
@ -41,7 +41,7 @@ class CustomReactionPresenter(
fun handleShowCustomReactionSheet(event: TimelineItem.Event) {
target.value = CustomReactionState.Target.Loading(event)
localCoroutineScope.launch {
recentEmojis = getRecentEmojis().getOrNull().orEmpty().toImmutableList()
recentEmojis = getRecentEmojis().getOrNull() ?: persistentListOf()
target.value = CustomReactionState.Target.Success(
event = event,
emojibaseStore = emojibaseProvider.emojibaseStore

View file

@ -1,18 +0,0 @@
/*
* Copyright 2023, 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.timeline.components.customreaction
import android.content.Context
import io.element.android.emojibasebindings.EmojibaseDatasource
import io.element.android.emojibasebindings.EmojibaseStore
class DefaultEmojibaseProvider(val context: Context) : EmojibaseProvider {
override val emojibaseStore: EmojibaseStore by lazy {
EmojibaseDatasource().load(context)
}
}

View file

@ -1,14 +0,0 @@
/*
* Copyright 2023, 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.timeline.components.customreaction
import io.element.android.emojibasebindings.EmojibaseStore
interface EmojibaseProvider {
val emojibaseStore: EmojibaseStore
}