From 45b5783b23d572d70098a233657d2b6be9ec8a8c Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Thu, 30 Oct 2025 16:27:51 +0100 Subject: [PATCH] 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`. --- .../io/element/android/x/di/AppModule.kt | 4 +- features/messages/impl/build.gradle.kts | 2 + .../messages/impl/MessagesPresenter.kt | 2 +- .../impl/actionlist/ActionListPresenter.kt | 10 +- .../actionlist/ActionListStateProvider.kt | 24 ++-- .../impl/actionlist/ActionListView.kt | 11 +- .../customreaction/CustomReactionPresenter.kt | 6 +- .../messages/impl/MessagesPresenterTest.kt | 2 +- .../actionlist/ActionListPresenterTest.kt | 108 +++++++++++++----- .../CustomReactionPresenterTest.kt | 4 +- .../customreaction/FakeEmojibaseProvider.kt | 16 --- .../components/media/WaveFormSamples.kt | 3 +- .../api/recentemojis/GetRecentEmojis.kt | 28 ----- libraries/recentemojis/api/build.gradle.kts | 26 +++++ .../recentemojis/api}/AddRecentEmoji.kt | 2 +- .../recentemojis/api}/EmojibaseProvider.kt | 4 +- .../recentemojis/api/GetRecentEmojis.kt | 17 +++ libraries/recentemojis/impl/build.gradle.kts | 35 ++++++ .../impl}/DefaultEmojibaseProvider.kt | 5 +- .../impl/DefaultGetRecentEmojis.kt | 37 ++++++ .../impl/DefaultGetRecentEmojisTest.kt | 68 +++++++++++ libraries/recentemojis/test/build.gradle.kts | 25 ++++ .../test/FakeEmojibaseProvider.kt | 22 ++++ .../kotlin/extension/DependencyHandleScope.kt | 1 + 24 files changed, 351 insertions(+), 111 deletions(-) delete mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/FakeEmojibaseProvider.kt delete mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/recentemojis/GetRecentEmojis.kt create mode 100644 libraries/recentemojis/api/build.gradle.kts rename libraries/{matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/recentemojis => recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api}/AddRecentEmoji.kt (91%) rename {features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction => libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api}/EmojibaseProvider.kt (69%) create mode 100644 libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/GetRecentEmojis.kt create mode 100644 libraries/recentemojis/impl/build.gradle.kts rename {features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction => libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl}/DefaultEmojibaseProvider.kt (75%) create mode 100644 libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl/DefaultGetRecentEmojis.kt create mode 100644 libraries/recentemojis/impl/src/test/kotlin/io/element/android/libraries/recentemojis/impl/DefaultGetRecentEmojisTest.kt create mode 100644 libraries/recentemojis/test/build.gradle.kts create mode 100644 libraries/recentemojis/test/src/main/kotlin/io/element/android/libraries/recentemojis/test/FakeEmojibaseProvider.kt diff --git a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt index d98a05321d..48b27d7879 100644 --- a/app/src/main/kotlin/io/element/android/x/di/AppModule.kt +++ b/app/src/main/kotlin/io/element/android/x/di/AppModule.kt @@ -18,8 +18,6 @@ import dev.zacsweers.metro.Provides import dev.zacsweers.metro.SingleIn import io.element.android.appconfig.ApplicationConfig 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.core.coroutine.CoroutineDispatchers 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.annotations.AppCoroutineScope 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.R import kotlinx.coroutines.CoroutineName diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 7c4230e603..cd72c4d84a 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -49,6 +49,7 @@ dependencies { implementation(projects.libraries.mediaupload.api) implementation(projects.libraries.permissions.api) implementation(projects.libraries.preferences.api) + implementation(projects.libraries.recentemojis.api) implementation(projects.libraries.roomselect.api) implementation(projects.libraries.voiceplayer.api) implementation(projects.libraries.voicerecorder.api) @@ -94,4 +95,5 @@ dependencies { testImplementation(projects.libraries.testtags) testImplementation(projects.features.poll.test) testImplementation(projects.libraries.eventformatter.test) + testImplementation(projects.libraries.recentemojis.test) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 503fd62fa5..9056437218 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -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 diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt index 25da8c2749..192ff30f39 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt @@ -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 diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt index eeab4aa3a0..1bee9d6478 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListStateProvider.kt @@ -27,6 +27,8 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList open class ActionListStateProvider : PreviewParameterProvider { + private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏") + override val values: Sequence get() { val reactionsState = aTimelineItemReactions(1, isHighlighted = true) @@ -42,7 +44,7 @@ open class ActionListStateProvider : PreviewParameterProvider { displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList(), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), anActionListState( @@ -58,7 +60,7 @@ open class ActionListStateProvider : PreviewParameterProvider { actions = aTimelineItemActionList( copyAction = TimelineItemAction.CopyCaption, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), anActionListState( @@ -73,7 +75,7 @@ open class ActionListStateProvider : PreviewParameterProvider { actions = aTimelineItemActionList( copyAction = TimelineItemAction.CopyCaption, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), anActionListState( @@ -88,7 +90,7 @@ open class ActionListStateProvider : PreviewParameterProvider { actions = aTimelineItemActionList( copyAction = null, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), anActionListState( @@ -103,7 +105,7 @@ open class ActionListStateProvider : PreviewParameterProvider { actions = aTimelineItemActionList( copyAction = TimelineItemAction.CopyCaption, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), anActionListState( @@ -118,7 +120,7 @@ open class ActionListStateProvider : PreviewParameterProvider { actions = aTimelineItemActionList( copyAction = null, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), anActionListState( @@ -131,7 +133,7 @@ open class ActionListStateProvider : PreviewParameterProvider { displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList(), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), anActionListState( @@ -144,7 +146,7 @@ open class ActionListStateProvider : PreviewParameterProvider { displayEmojiReactions = false, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList(), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ), ), anActionListState( @@ -157,7 +159,7 @@ open class ActionListStateProvider : PreviewParameterProvider { displayEmojiReactions = false, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemPollActionList(), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ), ), anActionListState( @@ -170,7 +172,7 @@ open class ActionListStateProvider : PreviewParameterProvider { displayEmojiReactions = true, verifiedUserSendFailure = VerifiedUserSendFailure.None, actions = aTimelineItemActionList(), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), anActionListState( @@ -180,7 +182,7 @@ open class ActionListStateProvider : PreviewParameterProvider { displayEmojiReactions = true, verifiedUserSendFailure = anUnsignedDeviceSendFailure(), actions = aTimelineItemActionList(), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ), ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt index a891e9d587..d322ac191f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt @@ -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 diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt index ba13c461e4..3d1cc93d58 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt @@ -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 diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index b25e097d48..34623a0c33 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -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.media.MediaSource 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.RoomMembersState 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.aTimelineItemDebugInfo 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.TextEditorState import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt index 52118a400d..e5786614ed 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt @@ -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.aRoomInfo 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.test import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -54,6 +56,8 @@ class ActionListPresenterTest { @get:Rule val warmUpRule = WarmUpRule() + private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏") + @Test fun `present - initial state`() = runTest { val presenter = createActionListPresenter(isDeveloperModeEnabled = true) @@ -95,7 +99,7 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.ViewSource, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -137,7 +141,7 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.ViewSource, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -185,7 +189,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -232,7 +236,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -279,7 +283,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -328,7 +332,7 @@ class ActionListPresenterTest { TimelineItemAction.ReportContent, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -377,7 +381,7 @@ class ActionListPresenterTest { TimelineItemAction.ReportContent, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -425,7 +429,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -472,7 +476,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -519,7 +523,7 @@ class ActionListPresenterTest { TimelineItemAction.CopyText, TimelineItemAction.ViewSource, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -563,7 +567,7 @@ class ActionListPresenterTest { TimelineItemAction.CopyText, TimelineItemAction.ViewSource, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -611,7 +615,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -663,7 +667,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -713,7 +717,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.ReportContent, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -754,7 +758,7 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.ViewSource, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -828,7 +832,7 @@ class ActionListPresenterTest { TimelineItemAction.CopyText, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -875,7 +879,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -929,7 +933,7 @@ class ActionListPresenterTest { TimelineItemAction.ViewSource, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) initialState.eventSink.invoke(ActionListEvents.Clear) @@ -1023,7 +1027,7 @@ class ActionListPresenterTest { TimelineItemAction.CopyText, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1068,7 +1072,7 @@ class ActionListPresenterTest { TimelineItemAction.Pin, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1112,7 +1116,7 @@ class ActionListPresenterTest { TimelineItemAction.Pin, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1155,7 +1159,7 @@ class ActionListPresenterTest { TimelineItemAction.Pin, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1201,7 +1205,7 @@ class ActionListPresenterTest { TimelineItemAction.Pin, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1239,7 +1243,7 @@ class ActionListPresenterTest { actions = persistentListOf( TimelineItemAction.ViewSource ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1317,7 +1321,7 @@ class ActionListPresenterTest { TimelineItemAction.Pin, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1371,7 +1375,7 @@ class ActionListPresenterTest { TimelineItemAction.Pin, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1426,7 +1430,7 @@ class ActionListPresenterTest { TimelineItemAction.Pin, TimelineItemAction.Redact, ), - recentEmojis = persistentListOf(), + recentEmojis = suggestedEmojis, ) ) } @@ -1478,11 +1482,56 @@ class ActionListPresenterTest { TimelineItemAction.Reply, 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( @@ -1490,6 +1539,7 @@ private fun createActionListPresenter( room: BaseRoom = FakeBaseRoom(), timelineMode: Timeline.Mode = Timeline.Mode.Live, featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(), + recentEmojis: GetRecentEmojis = GetRecentEmojis { Result.success(persistentListOf()) }, ): ActionListPresenter { val preferencesStore = InMemoryAppPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled) return DefaultActionListPresenter( @@ -1500,6 +1550,6 @@ private fun createActionListPresenter( dateFormatter = FakeDateFormatter(), timelineMode = timelineMode, featureFlagService = featureFlagService, - getRecentEmojis = { Result.success(persistentListOf()) }, + getRecentEmojis = recentEmojis, ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTest.kt index e34bbdcbef..f26c659bb5 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTest.kt @@ -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.aTimelineItemReactions 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 kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -25,7 +27,7 @@ class CustomReactionPresenterTest { private val presenter = CustomReactionPresenter( emojibaseProvider = FakeEmojibaseProvider(), - getRecentEmojis = { Result.success(emptyList()) }, + getRecentEmojis = { Result.success(persistentListOf()) }, ) @Test diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/FakeEmojibaseProvider.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/FakeEmojibaseProvider.kt deleted file mode 100644 index 498027bae6..0000000000 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/FakeEmojibaseProvider.kt +++ /dev/null @@ -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()) -} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveFormSamples.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveFormSamples.kt index 18efa2d3f3..72d4af60c0 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveFormSamples.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/media/WaveFormSamples.kt @@ -9,7 +9,6 @@ package io.element.android.libraries.designsystem.components.media import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import kotlinx.collections.immutable.toPersistentList object WaveFormSamples { val allRangeWaveForm = List(100) { it.toFloat() / 100 }.toImmutableList() @@ -26,5 +25,5 @@ object WaveFormSamples { 0.000f, 0.003f, ) - val longRealisticWaveForm = List(4) { realisticWaveForm }.flatten().toPersistentList() + val longRealisticWaveForm = List(4) { realisticWaveForm }.flatten().toImmutableList() } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/recentemojis/GetRecentEmojis.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/recentemojis/GetRecentEmojis.kt deleted file mode 100644 index 2a887ae297..0000000000 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/recentemojis/GetRecentEmojis.kt +++ /dev/null @@ -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> -} - -@ContributesBinding(SessionScope::class) -class DefaultGetRecentEmojis( - private val client: MatrixClient, - private val dispatchers: CoroutineDispatchers, -) : GetRecentEmojis { - override suspend operator fun invoke(): Result> = withContext(dispatchers.io) { - client.getRecentEmojis() - } -} diff --git a/libraries/recentemojis/api/build.gradle.kts b/libraries/recentemojis/api/build.gradle.kts new file mode 100644 index 0000000000..7302d965c8 --- /dev/null +++ b/libraries/recentemojis/api/build.gradle.kts @@ -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) +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/recentemojis/AddRecentEmoji.kt b/libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/AddRecentEmoji.kt similarity index 91% rename from libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/recentemojis/AddRecentEmoji.kt rename to libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/AddRecentEmoji.kt index da657ea78a..63731fa9cd 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/recentemojis/AddRecentEmoji.kt +++ b/libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/AddRecentEmoji.kt @@ -5,7 +5,7 @@ * 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 io.element.android.libraries.core.coroutine.CoroutineDispatchers diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseProvider.kt b/libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/EmojibaseProvider.kt similarity index 69% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseProvider.kt rename to libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/EmojibaseProvider.kt index ce394150b7..45896dc83c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/EmojibaseProvider.kt +++ b/libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/EmojibaseProvider.kt @@ -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 * 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 diff --git a/libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/GetRecentEmojis.kt b/libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/GetRecentEmojis.kt new file mode 100644 index 0000000000..bff2b98b33 --- /dev/null +++ b/libraries/recentemojis/api/src/main/kotlin/io/element/android/libraries/recentemojis/api/GetRecentEmojis.kt @@ -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> +} diff --git a/libraries/recentemojis/impl/build.gradle.kts b/libraries/recentemojis/impl/build.gradle.kts new file mode 100644 index 0000000000..c25539500e --- /dev/null +++ b/libraries/recentemojis/impl/build.gradle.kts @@ -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) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt b/libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl/DefaultEmojibaseProvider.kt similarity index 75% rename from features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt rename to libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl/DefaultEmojibaseProvider.kt index e07c78f578..33c42715bc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/DefaultEmojibaseProvider.kt +++ b/libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl/DefaultEmojibaseProvider.kt @@ -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 * 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 io.element.android.emojibasebindings.EmojibaseDatasource import io.element.android.emojibasebindings.EmojibaseStore +import io.element.android.libraries.recentemojis.api.EmojibaseProvider class DefaultEmojibaseProvider(val context: Context) : EmojibaseProvider { override val emojibaseStore: EmojibaseStore by lazy { diff --git a/libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl/DefaultGetRecentEmojis.kt b/libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl/DefaultGetRecentEmojis.kt new file mode 100644 index 0000000000..43e2fab8a8 --- /dev/null +++ b/libraries/recentemojis/impl/src/main/kotlin/io/element/android/libraries/recentemojis/impl/DefaultGetRecentEmojis.kt @@ -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> = 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() + } + } +} diff --git a/libraries/recentemojis/impl/src/test/kotlin/io/element/android/libraries/recentemojis/impl/DefaultGetRecentEmojisTest.kt b/libraries/recentemojis/impl/src/test/kotlin/io/element/android/libraries/recentemojis/impl/DefaultGetRecentEmojisTest.kt new file mode 100644 index 0000000000..8f9c15693f --- /dev/null +++ b/libraries/recentemojis/impl/src/test/kotlin/io/element/android/libraries/recentemojis/impl/DefaultGetRecentEmojisTest.kt @@ -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> = { Result.success(emptyList()) }, + emojibaseContents: ImmutableMap> = persistentMapOf(People to persistentListOf(emoji(":)"))), + ) = DefaultGetRecentEmojis( + client = FakeMatrixClient(getRecentEmojisLambda = recentEmojis), + dispatchers = testCoroutineDispatchers(), + emojibaseProvider = FakeEmojibaseProvider(emojibaseContents), + ) +} diff --git a/libraries/recentemojis/test/build.gradle.kts b/libraries/recentemojis/test/build.gradle.kts new file mode 100644 index 0000000000..4d851f410d --- /dev/null +++ b/libraries/recentemojis/test/build.gradle.kts @@ -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) +} diff --git a/libraries/recentemojis/test/src/main/kotlin/io/element/android/libraries/recentemojis/test/FakeEmojibaseProvider.kt b/libraries/recentemojis/test/src/main/kotlin/io/element/android/libraries/recentemojis/test/FakeEmojibaseProvider.kt new file mode 100644 index 0000000000..b882823d20 --- /dev/null +++ b/libraries/recentemojis/test/src/main/kotlin/io/element/android/libraries/recentemojis/test/FakeEmojibaseProvider.kt @@ -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> = mapOf(), +) : EmojibaseProvider { + override val emojibaseStore: EmojibaseStore + get() = EmojibaseStore(emojis.toPersistentMap()) +} diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index 3ca01ef2f6..52184ea318 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -119,6 +119,7 @@ fun DependencyHandlerScope.allLibrariesImpl() { implementation(project(":libraries:wellknown:impl")) implementation(project(":libraries:oidc:impl")) implementation(project(":libraries:workmanager:impl")) + implementation(project(":libraries:recentemojis:impl")) } fun DependencyHandlerScope.allServicesImpl() {