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

@@ -18,8 +18,6 @@ import dev.zacsweers.metro.Provides
import dev.zacsweers.metro.SingleIn import dev.zacsweers.metro.SingleIn
import io.element.android.appconfig.ApplicationConfig import io.element.android.appconfig.ApplicationConfig
import io.element.android.features.enterprise.api.EnterpriseService import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.features.messages.impl.timeline.components.customreaction.DefaultEmojibaseProvider
import io.element.android.features.messages.impl.timeline.components.customreaction.EmojibaseProvider
import io.element.android.libraries.androidutils.system.getVersionCodeFromManifest import io.element.android.libraries.androidutils.system.getVersionCodeFromManifest
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildMeta
@@ -29,6 +27,8 @@ import io.element.android.libraries.di.BaseDirectory
import io.element.android.libraries.di.CacheDirectory import io.element.android.libraries.di.CacheDirectory
import io.element.android.libraries.di.annotations.AppCoroutineScope import io.element.android.libraries.di.annotations.AppCoroutineScope
import io.element.android.libraries.di.annotations.ApplicationContext import io.element.android.libraries.di.annotations.ApplicationContext
import io.element.android.libraries.recentemojis.api.EmojibaseProvider
import io.element.android.libraries.recentemojis.impl.DefaultEmojibaseProvider
import io.element.android.x.BuildConfig import io.element.android.x.BuildConfig
import io.element.android.x.R import io.element.android.x.R
import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineName

View File

@@ -49,6 +49,7 @@ dependencies {
implementation(projects.libraries.mediaupload.api) implementation(projects.libraries.mediaupload.api)
implementation(projects.libraries.permissions.api) implementation(projects.libraries.permissions.api)
implementation(projects.libraries.preferences.api) implementation(projects.libraries.preferences.api)
implementation(projects.libraries.recentemojis.api)
implementation(projects.libraries.roomselect.api) implementation(projects.libraries.roomselect.api)
implementation(projects.libraries.voiceplayer.api) implementation(projects.libraries.voiceplayer.api)
implementation(projects.libraries.voicerecorder.api) implementation(projects.libraries.voicerecorder.api)
@@ -94,4 +95,5 @@ dependencies {
testImplementation(projects.libraries.testtags) testImplementation(projects.libraries.testtags)
testImplementation(projects.features.poll.test) testImplementation(projects.features.poll.test)
testImplementation(projects.libraries.eventformatter.test) testImplementation(projects.libraries.eventformatter.test)
testImplementation(projects.libraries.recentemojis.test)
} }

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.EncryptionService
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState 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.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.JoinedRoom
import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.RoomInfo 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.messages.reply.map
import io.element.android.libraries.matrix.ui.model.getAvatarData import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.room.getDirectRoomMember 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.textcomposer.model.MessageComposerMode
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.analytics.api.AnalyticsService 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.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.core.EventId 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.room.BaseRoom
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.preferences.api.store.AppPreferencesStore 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.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@@ -87,6 +87,8 @@ class DefaultActionListPresenter(
private val comparator = TimelineItemActionComparator() private val comparator = TimelineItemActionComparator()
private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏")
@Composable @Composable
override fun present(): ActionListState { override fun present(): ActionListState {
val localCoroutineScope = rememberCoroutineScope() val localCoroutineScope = rememberCoroutineScope()
@@ -146,6 +148,7 @@ class DefaultActionListPresenter(
val displayEmojiReactions = usersEventPermissions.canSendReaction && timelineItem.content.canReact() val displayEmojiReactions = usersEventPermissions.canSendReaction && timelineItem.content.canReact()
if (actions.isNotEmpty() || displayEmojiReactions || verifiedUserSendFailure != VerifiedUserSendFailure.None) { if (actions.isNotEmpty() || displayEmojiReactions || verifiedUserSendFailure != VerifiedUserSendFailure.None) {
val recentEmojis = getRecentEmojis().getOrNull()?.toImmutableList() ?: persistentListOf()
target.value = ActionListState.Target.Success( target.value = ActionListState.Target.Success(
event = timelineItem, event = timelineItem,
sentTimeFull = dateFormatter.format( sentTimeFull = dateFormatter.format(
@@ -156,7 +159,10 @@ class DefaultActionListPresenter(
displayEmojiReactions = displayEmojiReactions, displayEmojiReactions = displayEmojiReactions,
verifiedUserSendFailure = verifiedUserSendFailure, verifiedUserSendFailure = verifiedUserSendFailure,
actions = actions.toImmutableList(), 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 { } else {
target.value = ActionListState.Target.None target.value = ActionListState.Target.None

View File

@@ -27,6 +27,8 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
open class ActionListStateProvider : PreviewParameterProvider<ActionListState> { open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏")
override val values: Sequence<ActionListState> override val values: Sequence<ActionListState>
get() { get() {
val reactionsState = aTimelineItemReactions(1, isHighlighted = true) val reactionsState = aTimelineItemReactions(1, isHighlighted = true)
@@ -42,7 +44,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = true, displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None, verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = aTimelineItemActionList(), actions = aTimelineItemActionList(),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
), ),
anActionListState( anActionListState(
@@ -58,7 +60,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
actions = aTimelineItemActionList( actions = aTimelineItemActionList(
copyAction = TimelineItemAction.CopyCaption, copyAction = TimelineItemAction.CopyCaption,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
), ),
anActionListState( anActionListState(
@@ -73,7 +75,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
actions = aTimelineItemActionList( actions = aTimelineItemActionList(
copyAction = TimelineItemAction.CopyCaption, copyAction = TimelineItemAction.CopyCaption,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
), ),
anActionListState( anActionListState(
@@ -88,7 +90,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
actions = aTimelineItemActionList( actions = aTimelineItemActionList(
copyAction = null, copyAction = null,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
), ),
anActionListState( anActionListState(
@@ -103,7 +105,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
actions = aTimelineItemActionList( actions = aTimelineItemActionList(
copyAction = TimelineItemAction.CopyCaption, copyAction = TimelineItemAction.CopyCaption,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
), ),
anActionListState( anActionListState(
@@ -118,7 +120,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
actions = aTimelineItemActionList( actions = aTimelineItemActionList(
copyAction = null, copyAction = null,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
), ),
anActionListState( anActionListState(
@@ -131,7 +133,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = true, displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None, verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = aTimelineItemActionList(), actions = aTimelineItemActionList(),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
), ),
anActionListState( anActionListState(
@@ -144,7 +146,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = false, displayEmojiReactions = false,
verifiedUserSendFailure = VerifiedUserSendFailure.None, verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = aTimelineItemActionList(), actions = aTimelineItemActionList(),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
), ),
), ),
anActionListState( anActionListState(
@@ -157,7 +159,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = false, displayEmojiReactions = false,
verifiedUserSendFailure = VerifiedUserSendFailure.None, verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = aTimelineItemPollActionList(), actions = aTimelineItemPollActionList(),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
), ),
), ),
anActionListState( anActionListState(
@@ -170,7 +172,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = true, displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None, verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = aTimelineItemActionList(), actions = aTimelineItemActionList(),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
), ),
anActionListState( anActionListState(
@@ -180,7 +182,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
displayEmojiReactions = true, displayEmojiReactions = true,
verifiedUserSendFailure = anUnsignedDeviceSendFailure(), verifiedUserSendFailure = anUnsignedDeviceSendFailure(),
actions = aTimelineItemActionList(), 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.matrix.ui.messages.sender.SenderNameMode
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -345,7 +343,6 @@ private fun MessageSummary(
} }
private val emojiRippleRadius = 24.dp private val emojiRippleRadius = 24.dp
private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏")
@Composable @Composable
private fun EmojiReactionsRow( private fun EmojiReactionsRow(
@@ -360,12 +357,6 @@ private fun EmojiReactionsRow(
) { ) {
val backgroundColor = ElementTheme.colors.bgCanvasDefault val backgroundColor = ElementTheme.colors.bgCanvasDefault
val emojis = remember(recentEmojis) {
(suggestedEmojis + recentEmojis.filter { it !in suggestedEmojis })
.take(100)
.toImmutableList()
}
LazyRow( LazyRow(
modifier = Modifier modifier = Modifier
.weight(1f, fill = true) .weight(1f, fill = true)
@@ -388,7 +379,7 @@ private fun EmojiReactionsRow(
contentPadding = PaddingValues(horizontal = 16.dp), contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
items(emojis) { emoji -> items(recentEmojis) { emoji ->
val isHighlighted = highlightedEmojis.contains(emoji) val isHighlighted = highlightedEmojis.contains(emoji)
EmojiButton( EmojiButton(
modifier = Modifier modifier = Modifier

View File

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

View File

@@ -59,7 +59,6 @@ import io.element.android.libraries.matrix.api.core.toThreadId
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.permalink.PermalinkParser 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.MessageEventType import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.RoomMembersState import io.element.android.libraries.matrix.api.room.RoomMembersState
import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.api.room.RoomMembershipState
@@ -89,6 +88,7 @@ import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.matrix.test.timeline.aTimelineItemDebugInfo import io.element.android.libraries.matrix.test.timeline.aTimelineItemDebugInfo
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
import io.element.android.libraries.recentemojis.api.AddRecentEmoji
import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.libraries.textcomposer.model.TextEditorState import io.element.android.libraries.textcomposer.model.TextEditorState
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown

View File

@@ -42,9 +42,11 @@ import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeBaseRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
import io.element.android.libraries.recentemojis.api.GetRecentEmojis
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.test import io.element.android.tests.testutils.test
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@@ -54,6 +56,8 @@ class ActionListPresenterTest {
@get:Rule @get:Rule
val warmUpRule = WarmUpRule() val warmUpRule = WarmUpRule()
private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏")
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
@@ -95,7 +99,7 @@ class ActionListPresenterTest {
actions = persistentListOf( actions = persistentListOf(
TimelineItemAction.ViewSource, TimelineItemAction.ViewSource,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -137,7 +141,7 @@ class ActionListPresenterTest {
actions = persistentListOf( actions = persistentListOf(
TimelineItemAction.ViewSource, TimelineItemAction.ViewSource,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -185,7 +189,7 @@ class ActionListPresenterTest {
TimelineItemAction.ViewSource, TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent, TimelineItemAction.ReportContent,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -232,7 +236,7 @@ class ActionListPresenterTest {
TimelineItemAction.ViewSource, TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent, TimelineItemAction.ReportContent,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -279,7 +283,7 @@ class ActionListPresenterTest {
TimelineItemAction.ViewSource, TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent, TimelineItemAction.ReportContent,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -328,7 +332,7 @@ class ActionListPresenterTest {
TimelineItemAction.ReportContent, TimelineItemAction.ReportContent,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -377,7 +381,7 @@ class ActionListPresenterTest {
TimelineItemAction.ReportContent, TimelineItemAction.ReportContent,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -425,7 +429,7 @@ class ActionListPresenterTest {
TimelineItemAction.ViewSource, TimelineItemAction.ViewSource,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -472,7 +476,7 @@ class ActionListPresenterTest {
TimelineItemAction.ViewSource, TimelineItemAction.ViewSource,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -519,7 +523,7 @@ class ActionListPresenterTest {
TimelineItemAction.CopyText, TimelineItemAction.CopyText,
TimelineItemAction.ViewSource, TimelineItemAction.ViewSource,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -563,7 +567,7 @@ class ActionListPresenterTest {
TimelineItemAction.CopyText, TimelineItemAction.CopyText,
TimelineItemAction.ViewSource, TimelineItemAction.ViewSource,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -611,7 +615,7 @@ class ActionListPresenterTest {
TimelineItemAction.ViewSource, TimelineItemAction.ViewSource,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -663,7 +667,7 @@ class ActionListPresenterTest {
TimelineItemAction.ViewSource, TimelineItemAction.ViewSource,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -713,7 +717,7 @@ class ActionListPresenterTest {
TimelineItemAction.ViewSource, TimelineItemAction.ViewSource,
TimelineItemAction.ReportContent, TimelineItemAction.ReportContent,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -754,7 +758,7 @@ class ActionListPresenterTest {
actions = persistentListOf( actions = persistentListOf(
TimelineItemAction.ViewSource, TimelineItemAction.ViewSource,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -828,7 +832,7 @@ class ActionListPresenterTest {
TimelineItemAction.CopyText, TimelineItemAction.CopyText,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -875,7 +879,7 @@ class ActionListPresenterTest {
TimelineItemAction.ViewSource, TimelineItemAction.ViewSource,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -929,7 +933,7 @@ class ActionListPresenterTest {
TimelineItemAction.ViewSource, TimelineItemAction.ViewSource,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvents.Clear)
@@ -1023,7 +1027,7 @@ class ActionListPresenterTest {
TimelineItemAction.CopyText, TimelineItemAction.CopyText,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
} }
@@ -1068,7 +1072,7 @@ class ActionListPresenterTest {
TimelineItemAction.Pin, TimelineItemAction.Pin,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
} }
@@ -1112,7 +1116,7 @@ class ActionListPresenterTest {
TimelineItemAction.Pin, TimelineItemAction.Pin,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
} }
@@ -1155,7 +1159,7 @@ class ActionListPresenterTest {
TimelineItemAction.Pin, TimelineItemAction.Pin,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
} }
@@ -1201,7 +1205,7 @@ class ActionListPresenterTest {
TimelineItemAction.Pin, TimelineItemAction.Pin,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
} }
@@ -1239,7 +1243,7 @@ class ActionListPresenterTest {
actions = persistentListOf( actions = persistentListOf(
TimelineItemAction.ViewSource TimelineItemAction.ViewSource
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
} }
@@ -1317,7 +1321,7 @@ class ActionListPresenterTest {
TimelineItemAction.Pin, TimelineItemAction.Pin,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
} }
@@ -1371,7 +1375,7 @@ class ActionListPresenterTest {
TimelineItemAction.Pin, TimelineItemAction.Pin,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
} }
@@ -1426,7 +1430,7 @@ class ActionListPresenterTest {
TimelineItemAction.Pin, TimelineItemAction.Pin,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
} }
@@ -1478,11 +1482,56 @@ class ActionListPresenterTest {
TimelineItemAction.Reply, TimelineItemAction.Reply,
TimelineItemAction.Redact, TimelineItemAction.Redact,
), ),
recentEmojis = persistentListOf(), recentEmojis = suggestedEmojis,
) )
) )
} }
} }
@Test
fun `present - recentEmojis merges suggested and recent emojis`() = runTest {
val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏")
val otherEmojis = (0..100).map { it.toString() }
val presenter = createActionListPresenter(
isDeveloperModeEnabled = false,
recentEmojis = GetRecentEmojis { Result.success((listOf("👍️", ":)", "❤️") + otherEmojis).toImmutableList()) },
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val messageEvent = aMessageEvent(
eventId = null,
transactionId = A_TRANSACTION_ID,
isMine = true,
isEditable = false,
content = aTimelineItemVoiceContent(
caption = null,
),
)
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
userEventPermissions = aUserEventPermissions(
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
canPinUnpin = true
)
)
)
val successState = awaitItem()
assertThat(successState.target).isInstanceOf(ActionListState.Target.Success::class.java)
// Check items are deduplicated between suggested and recent emojis and we take at most 100 items
val expectedEmojis = (suggestedEmojis + persistentListOf(":)") + otherEmojis).take(100)
assertThat((successState.target as ActionListState.Target.Success).recentEmojis)
.isEqualTo(expectedEmojis)
}
}
} }
private fun createActionListPresenter( private fun createActionListPresenter(
@@ -1490,6 +1539,7 @@ private fun createActionListPresenter(
room: BaseRoom = FakeBaseRoom(), room: BaseRoom = FakeBaseRoom(),
timelineMode: Timeline.Mode = Timeline.Mode.Live, timelineMode: Timeline.Mode = Timeline.Mode.Live,
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(), featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(),
recentEmojis: GetRecentEmojis = GetRecentEmojis { Result.success(persistentListOf()) },
): ActionListPresenter { ): ActionListPresenter {
val preferencesStore = InMemoryAppPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled) val preferencesStore = InMemoryAppPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled)
return DefaultActionListPresenter( return DefaultActionListPresenter(
@@ -1500,6 +1550,6 @@ private fun createActionListPresenter(
dateFormatter = FakeDateFormatter(), dateFormatter = FakeDateFormatter(),
timelineMode = timelineMode, timelineMode = timelineMode,
featureFlagService = featureFlagService, featureFlagService = featureFlagService,
getRecentEmojis = { Result.success(persistentListOf()) }, getRecentEmojis = recentEmojis,
) )
} }

View File

@@ -14,7 +14,9 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.recentemojis.test.FakeEmojibaseProvider
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@@ -25,7 +27,7 @@ class CustomReactionPresenterTest {
private val presenter = CustomReactionPresenter( private val presenter = CustomReactionPresenter(
emojibaseProvider = FakeEmojibaseProvider(), emojibaseProvider = FakeEmojibaseProvider(),
getRecentEmojis = { Result.success(emptyList()) }, getRecentEmojis = { Result.success(persistentListOf()) },
) )
@Test @Test

View File

@@ -1,16 +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
import kotlinx.collections.immutable.persistentMapOf
class FakeEmojibaseProvider : EmojibaseProvider {
override val emojibaseStore: EmojibaseStore
get() = EmojibaseStore(persistentMapOf())
}

View File

@@ -9,7 +9,6 @@ package io.element.android.libraries.designsystem.components.media
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList
object WaveFormSamples { object WaveFormSamples {
val allRangeWaveForm = List(100) { it.toFloat() / 100 }.toImmutableList() val allRangeWaveForm = List(100) { it.toFloat() / 100 }.toImmutableList()
@@ -26,5 +25,5 @@ object WaveFormSamples {
0.000f, 0.003f, 0.000f, 0.003f,
) )
val longRealisticWaveForm = List(4) { realisticWaveForm }.flatten().toPersistentList() val longRealisticWaveForm = List(4) { realisticWaveForm }.flatten().toImmutableList()
} }

View File

@@ -1,28 +0,0 @@
/*
* Copyright 2025 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.libraries.matrix.api.recentemojis
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient
import kotlinx.coroutines.withContext
fun interface GetRecentEmojis {
suspend operator fun invoke(): Result<List<String>>
}
@ContributesBinding(SessionScope::class)
class DefaultGetRecentEmojis(
private val client: MatrixClient,
private val dispatchers: CoroutineDispatchers,
) : GetRecentEmojis {
override suspend operator fun invoke(): Result<List<String>> = withContext(dispatchers.io) {
client.getRecentEmojis()
}
}

View File

@@ -0,0 +1,26 @@
import extension.setupDependencyInjection
/*
* Copyright 2025 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.
*/
plugins {
id("io.element.android-library")
}
android {
namespace = "io.element.android.libraries.recentemojis.api"
}
setupDependencyInjection()
dependencies {
implementation(projects.libraries.architecture)
implementation(projects.libraries.matrix.api)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.matrix.emojibase.bindings)
}

View File

@@ -5,7 +5,7 @@
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
package io.element.android.libraries.matrix.api.recentemojis package io.element.android.libraries.recentemojis.api
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers

View File

@@ -1,11 +1,11 @@
/* /*
* Copyright 2023, 2024 New Vector Ltd. * Copyright 2025 New Vector Ltd.
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
package io.element.android.features.messages.impl.timeline.components.customreaction package io.element.android.libraries.recentemojis.api
import io.element.android.emojibasebindings.EmojibaseStore import io.element.android.emojibasebindings.EmojibaseStore

View File

@@ -0,0 +1,17 @@
/*
* Copyright 2025 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.libraries.recentemojis.api
import kotlinx.collections.immutable.ImmutableList
/**
* Returns the list of recently used emojis for reactions.
*/
fun interface GetRecentEmojis {
suspend operator fun invoke(): Result<ImmutableList<String>>
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2025 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.
*/
import extension.setupDependencyInjection
plugins {
id("io.element.android-compose-library")
id("kotlin-parcelize")
}
android {
namespace = "io.element.android.libraries.recentemojis.impl"
}
setupDependencyInjection()
dependencies {
api(projects.libraries.recentemojis.api)
implementation(projects.libraries.matrix.api)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.matrix.emojibase.bindings)
testImplementation(projects.libraries.recentemojis.test)
testImplementation(libs.test.junit)
testImplementation(libs.coroutines.test)
testImplementation(libs.molecule.runtime)
testImplementation(libs.test.truth)
testImplementation(libs.test.turbine)
testImplementation(projects.libraries.matrix.test)
testImplementation(projects.tests.testutils)
}

View File

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

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2025 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.libraries.recentemojis.impl
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient
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.toImmutableList
import kotlinx.coroutines.withContext
@ContributesBinding(SessionScope::class)
class DefaultGetRecentEmojis(
private val client: MatrixClient,
private val dispatchers: CoroutineDispatchers,
private val emojibaseProvider: EmojibaseProvider,
) : GetRecentEmojis {
override suspend operator fun invoke(): Result<ImmutableList<String>> = withContext(dispatchers.io) {
val allEmojis = emojibaseProvider.emojibaseStore.allEmojis
client.getRecentEmojis()
.map { emojis ->
// Remove any possible duplicates
emojis.distinct()
// Return only those emojis that are valid
.filter { recent -> allEmojis.any { recent == it.unicode } }
.toImmutableList()
}
}
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2025 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.libraries.recentemojis.impl
import com.google.common.truth.Truth.assertThat
import io.element.android.emojibasebindings.Emoji
import io.element.android.emojibasebindings.EmojibaseCategory
import io.element.android.emojibasebindings.EmojibaseCategory.People
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.recentemojis.test.FakeEmojibaseProvider
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
class DefaultGetRecentEmojisTest {
@Test
fun `invoke - deduplicates results`() = runTest {
val recentEmojiResult = persistentListOf(":)", ":D", ":)")
val getRecentEmojis = createDefaultGetRecentEmojis(
recentEmojis = { Result.success(recentEmojiResult) },
emojibaseContents = persistentMapOf(People to recentEmojiResult.map { emoji(it) }.toImmutableList())
)
assertThat(getRecentEmojis()).isEqualTo(Result.success(persistentListOf(":)", ":D")))
}
@Test
fun `invoke - removes non-standard emojis`() = runTest {
val recentEmojiResult = persistentListOf(":)", ":D", "Custom reaction")
val getRecentEmojis = createDefaultGetRecentEmojis(
recentEmojis = { Result.success(recentEmojiResult) },
emojibaseContents = persistentMapOf(
People to persistentListOf(emoji(":)"), emoji(":D"))
)
)
assertThat(getRecentEmojis()).isEqualTo(Result.success(persistentListOf(":)", ":D")))
}
private fun emoji(unicode: String) = Emoji(
hexcode = "",
label = "",
tags = null,
shortcodes = persistentListOf(),
unicode = unicode,
skins = null,
)
private fun TestScope.createDefaultGetRecentEmojis(
recentEmojis: () -> Result<List<String>> = { Result.success(emptyList()) },
emojibaseContents: ImmutableMap<EmojibaseCategory, ImmutableList<Emoji>> = persistentMapOf(People to persistentListOf(emoji(":)"))),
) = DefaultGetRecentEmojis(
client = FakeMatrixClient(getRecentEmojisLambda = recentEmojis),
dispatchers = testCoroutineDispatchers(),
emojibaseProvider = FakeEmojibaseProvider(emojibaseContents),
)
}

View File

@@ -0,0 +1,25 @@
/*
* 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.
*/
plugins {
id("io.element.android-library")
}
android {
namespace = "io.element.android.libraries.recentemojis.test"
}
dependencies {
api(projects.libraries.matrix.api)
api(libs.coroutines.core)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.coroutines.test)
implementation(projects.tests.testutils)
implementation(projects.libraries.recentemojis.api)
implementation(libs.matrix.emojibase.bindings)
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2025 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.libraries.recentemojis.test
import io.element.android.emojibasebindings.Emoji
import io.element.android.emojibasebindings.EmojibaseCategory
import io.element.android.emojibasebindings.EmojibaseStore
import io.element.android.libraries.recentemojis.api.EmojibaseProvider
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentMap
class FakeEmojibaseProvider(
val emojis: Map<EmojibaseCategory, ImmutableList<Emoji>> = mapOf(),
) : EmojibaseProvider {
override val emojibaseStore: EmojibaseStore
get() = EmojibaseStore(emojis.toPersistentMap())
}

View File

@@ -119,6 +119,7 @@ fun DependencyHandlerScope.allLibrariesImpl() {
implementation(project(":libraries:wellknown:impl")) implementation(project(":libraries:wellknown:impl"))
implementation(project(":libraries:oidc:impl")) implementation(project(":libraries:oidc:impl"))
implementation(project(":libraries:workmanager:impl")) implementation(project(":libraries:workmanager:impl"))
implementation(project(":libraries:recentemojis:impl"))
} }
fun DependencyHandlerScope.allServicesImpl() { fun DependencyHandlerScope.allServicesImpl() {