Skip to content

Commit 0fa66f7

Browse files
committed
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`.
1 parent 9baf948 commit 0fa66f7

File tree

22 files changed

+310
-68
lines changed

22 files changed

+310
-68
lines changed

app/src/main/kotlin/io/element/android/x/di/AppModule.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ import dev.zacsweers.metro.Provides
1818
import dev.zacsweers.metro.SingleIn
1919
import io.element.android.appconfig.ApplicationConfig
2020
import io.element.android.features.enterprise.api.EnterpriseService
21-
import io.element.android.features.messages.impl.timeline.components.customreaction.DefaultEmojibaseProvider
22-
import io.element.android.features.messages.impl.timeline.components.customreaction.EmojibaseProvider
2321
import io.element.android.libraries.androidutils.system.getVersionCodeFromManifest
2422
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
2523
import io.element.android.libraries.core.meta.BuildMeta
@@ -29,6 +27,8 @@ import io.element.android.libraries.di.BaseDirectory
2927
import io.element.android.libraries.di.CacheDirectory
3028
import io.element.android.libraries.di.annotations.AppCoroutineScope
3129
import io.element.android.libraries.di.annotations.ApplicationContext
30+
import io.element.android.libraries.recentemojis.api.EmojibaseProvider
31+
import io.element.android.libraries.recentemojis.impl.DefaultEmojibaseProvider
3232
import io.element.android.x.BuildConfig
3333
import io.element.android.x.R
3434
import kotlinx.coroutines.CoroutineName

features/messages/impl/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ dependencies {
4848
implementation(projects.libraries.mediaupload.api)
4949
implementation(projects.libraries.permissions.api)
5050
implementation(projects.libraries.preferences.api)
51+
implementation(projects.libraries.recentemojis.api)
5152
implementation(projects.libraries.roomselect.api)
5253
implementation(projects.libraries.voiceplayer.api)
5354
implementation(projects.libraries.voicerecorder.api)
@@ -92,4 +93,5 @@ dependencies {
9293
testImplementation(projects.libraries.testtags)
9394
testImplementation(projects.features.poll.test)
9495
testImplementation(projects.libraries.eventformatter.test)
96+
testImplementation(projects.libraries.recentemojis.test)
9597
}

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ import io.element.android.libraries.matrix.api.core.toThreadId
7171
import io.element.android.libraries.matrix.api.encryption.EncryptionService
7272
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
7373
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
74-
import io.element.android.libraries.matrix.api.recentemojis.AddRecentEmoji
7574
import io.element.android.libraries.matrix.api.room.JoinedRoom
7675
import io.element.android.libraries.matrix.api.room.MessageEventType
7776
import io.element.android.libraries.matrix.api.room.RoomInfo
@@ -85,6 +84,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransa
8584
import io.element.android.libraries.matrix.ui.messages.reply.map
8685
import io.element.android.libraries.matrix.ui.model.getAvatarData
8786
import io.element.android.libraries.matrix.ui.room.getDirectRoomMember
87+
import io.element.android.libraries.recentemojis.api.AddRecentEmoji
8888
import io.element.android.libraries.textcomposer.model.MessageComposerMode
8989
import io.element.android.libraries.ui.strings.CommonStrings
9090
import io.element.android.services.analytics.api.AnalyticsService

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ import io.element.android.libraries.di.RoomScope
4343
import io.element.android.libraries.featureflag.api.FeatureFlagService
4444
import io.element.android.libraries.featureflag.api.FeatureFlags
4545
import io.element.android.libraries.matrix.api.core.EventId
46-
import io.element.android.libraries.matrix.api.recentemojis.GetRecentEmojis
4746
import io.element.android.libraries.matrix.api.room.BaseRoom
4847
import io.element.android.libraries.matrix.api.timeline.Timeline
4948
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
49+
import io.element.android.libraries.recentemojis.api.GetRecentEmojis
5050
import kotlinx.collections.immutable.ImmutableList
5151
import kotlinx.collections.immutable.persistentListOf
5252
import kotlinx.collections.immutable.toImmutableList
@@ -87,6 +87,8 @@ class DefaultActionListPresenter(
8787

8888
private val comparator = TimelineItemActionComparator()
8989

90+
private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏")
91+
9092
@Composable
9193
override fun present(): ActionListState {
9294
val localCoroutineScope = rememberCoroutineScope()
@@ -146,6 +148,7 @@ class DefaultActionListPresenter(
146148
val displayEmojiReactions = usersEventPermissions.canSendReaction && timelineItem.content.canReact()
147149

148150
if (actions.isNotEmpty() || displayEmojiReactions || verifiedUserSendFailure != VerifiedUserSendFailure.None) {
151+
val recentEmojis = getRecentEmojis().getOrNull()?.toImmutableList() ?: persistentListOf()
149152
target.value = ActionListState.Target.Success(
150153
event = timelineItem,
151154
sentTimeFull = dateFormatter.format(
@@ -156,7 +159,10 @@ class DefaultActionListPresenter(
156159
displayEmojiReactions = displayEmojiReactions,
157160
verifiedUserSendFailure = verifiedUserSendFailure,
158161
actions = actions.toImmutableList(),
159-
recentEmojis = getRecentEmojis().getOrNull()?.toImmutableList() ?: persistentListOf()
162+
// Merge suggested and recent emojis, removing duplicates and returning at most 100
163+
recentEmojis = (suggestedEmojis + recentEmojis).distinct()
164+
.take(100)
165+
.toImmutableList()
160166
)
161167
} else {
162168
target.value = ActionListState.Target.None

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,6 @@ private fun MessageSummary(
345345
}
346346

347347
private val emojiRippleRadius = 24.dp
348-
private val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏")
349348

350349
@Composable
351350
private fun EmojiReactionsRow(
@@ -360,12 +359,6 @@ private fun EmojiReactionsRow(
360359
) {
361360
val backgroundColor = ElementTheme.colors.bgCanvasDefault
362361

363-
val emojis = remember(recentEmojis) {
364-
(suggestedEmojis + recentEmojis.filter { it !in suggestedEmojis })
365-
.take(100)
366-
.toImmutableList()
367-
}
368-
369362
LazyRow(
370363
modifier = Modifier
371364
.weight(1f, fill = true)
@@ -388,7 +381,7 @@ private fun EmojiReactionsRow(
388381
contentPadding = PaddingValues(horizontal = 16.dp),
389382
horizontalArrangement = Arrangement.spacedBy(8.dp),
390383
) {
391-
items(emojis) { emoji ->
384+
items(recentEmojis) { emoji ->
392385
val isHighlighted = highlightedEmojis.contains(emoji)
393386
EmojiButton(
394387
modifier = Modifier

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ import androidx.compose.runtime.setValue
1717
import dev.zacsweers.metro.Inject
1818
import io.element.android.features.messages.impl.timeline.model.TimelineItem
1919
import io.element.android.libraries.architecture.Presenter
20-
import io.element.android.libraries.matrix.api.recentemojis.GetRecentEmojis
20+
import io.element.android.libraries.recentemojis.api.EmojibaseProvider
21+
import io.element.android.libraries.recentemojis.api.GetRecentEmojis
2122
import kotlinx.collections.immutable.ImmutableList
2223
import kotlinx.collections.immutable.persistentListOf
23-
import kotlinx.collections.immutable.toImmutableList
2424
import kotlinx.collections.immutable.toImmutableSet
2525
import kotlinx.coroutines.launch
2626

@@ -41,7 +41,7 @@ class CustomReactionPresenter(
4141
fun handleShowCustomReactionSheet(event: TimelineItem.Event) {
4242
target.value = CustomReactionState.Target.Loading(event)
4343
localCoroutineScope.launch {
44-
recentEmojis = getRecentEmojis().getOrNull().orEmpty().toImmutableList()
44+
recentEmojis = getRecentEmojis().getOrNull() ?: persistentListOf()
4545
target.value = CustomReactionState.Target.Success(
4646
event = event,
4747
emojibaseStore = emojibaseProvider.emojibaseStore

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ import io.element.android.libraries.matrix.api.core.toThreadId
5757
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
5858
import io.element.android.libraries.matrix.api.media.MediaSource
5959
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
60-
import io.element.android.libraries.matrix.api.recentemojis.AddRecentEmoji
6160
import io.element.android.libraries.matrix.api.room.MessageEventType
6261
import io.element.android.libraries.matrix.api.room.RoomMembersState
6362
import io.element.android.libraries.matrix.api.room.RoomMembershipState
@@ -87,6 +86,7 @@ import io.element.android.libraries.matrix.test.room.aRoomMember
8786
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
8887
import io.element.android.libraries.matrix.test.timeline.aTimelineItemDebugInfo
8988
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
89+
import io.element.android.libraries.recentemojis.api.AddRecentEmoji
9090
import io.element.android.libraries.textcomposer.model.MessageComposerMode
9191
import io.element.android.libraries.textcomposer.model.TextEditorState
9292
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import app.cash.molecule.RecompositionMode
1111
import app.cash.molecule.moleculeFlow
1212
import app.cash.turbine.test
1313
import com.google.common.truth.Truth.assertThat
14+
import io.element.android.emojibasebindings.Emoji
15+
import io.element.android.emojibasebindings.EmojibaseCategory
1416
import io.element.android.features.messages.impl.aUserEventPermissions
1517
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
1618
import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor
@@ -42,9 +44,12 @@ import io.element.android.libraries.matrix.test.A_USER_ID
4244
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
4345
import io.element.android.libraries.matrix.test.room.aRoomInfo
4446
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
47+
import io.element.android.libraries.recentemojis.api.GetRecentEmojis
48+
import io.element.android.libraries.recentemojis.test.FakeEmojibaseProvider
4549
import io.element.android.tests.testutils.WarmUpRule
4650
import io.element.android.tests.testutils.test
4751
import kotlinx.collections.immutable.persistentListOf
52+
import kotlinx.collections.immutable.toPersistentList
4853
import kotlinx.coroutines.test.runTest
4954
import org.junit.Rule
5055
import org.junit.Test
@@ -1483,13 +1488,59 @@ class ActionListPresenterTest {
14831488
)
14841489
}
14851490
}
1491+
1492+
@Test
1493+
fun `present - recentEmojis merges suggested and recent emojis`() = runTest {
1494+
val suggestedEmojis = persistentListOf("👍️", "👎️", "🔥", "❤️", "👏")
1495+
val otherEmojis = (0..100).map { it.toString() }
1496+
1497+
val presenter = createActionListPresenter(
1498+
isDeveloperModeEnabled = false,
1499+
recentEmojis = GetRecentEmojis { Result.success((listOf("👍️", ":)", "❤️") + otherEmojis).toPersistentList()) },
1500+
)
1501+
moleculeFlow(RecompositionMode.Immediate) {
1502+
presenter.present()
1503+
}.test {
1504+
val initialState = awaitItem()
1505+
val messageEvent = aMessageEvent(
1506+
eventId = null,
1507+
transactionId = A_TRANSACTION_ID,
1508+
isMine = true,
1509+
isEditable = false,
1510+
content = aTimelineItemVoiceContent(
1511+
caption = null,
1512+
),
1513+
)
1514+
1515+
initialState.eventSink.invoke(
1516+
ActionListEvents.ComputeForMessage(
1517+
event = messageEvent,
1518+
userEventPermissions = aUserEventPermissions(
1519+
canRedactOwn = true,
1520+
canRedactOther = false,
1521+
canSendMessage = true,
1522+
canSendReaction = true,
1523+
canPinUnpin = true
1524+
)
1525+
)
1526+
)
1527+
val successState = awaitItem()
1528+
assertThat(successState.target).isInstanceOf(ActionListState.Target.Success::class.java)
1529+
1530+
// Check items are deduplicated between suggested and recent emojis and we take at most 100 items
1531+
val expectedEmojis = (suggestedEmojis + persistentListOf(":)") + otherEmojis).take(100)
1532+
assertThat((successState.target as ActionListState.Target.Success).recentEmojis)
1533+
.isEqualTo(expectedEmojis)
1534+
}
1535+
}
14861536
}
14871537

14881538
private fun createActionListPresenter(
14891539
isDeveloperModeEnabled: Boolean,
14901540
room: BaseRoom = FakeBaseRoom(),
14911541
timelineMode: Timeline.Mode = Timeline.Mode.Live,
14921542
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(),
1543+
recentEmojis: GetRecentEmojis = GetRecentEmojis { Result.success(persistentListOf()) },
14931544
): ActionListPresenter {
14941545
val preferencesStore = InMemoryAppPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled)
14951546
return DefaultActionListPresenter(
@@ -1500,6 +1551,6 @@ private fun createActionListPresenter(
15001551
dateFormatter = FakeDateFormatter(),
15011552
timelineMode = timelineMode,
15021553
featureFlagService = featureFlagService,
1503-
getRecentEmojis = { Result.success(persistentListOf()) },
1554+
getRecentEmojis = recentEmojis,
15041555
)
15051556
}

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenterTest.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import com.google.common.truth.Truth.assertThat
1414
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
1515
import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
1616
import io.element.android.libraries.matrix.test.AN_EVENT_ID
17+
import io.element.android.libraries.recentemojis.test.FakeEmojibaseProvider
1718
import io.element.android.tests.testutils.WarmUpRule
19+
import kotlinx.collections.immutable.persistentListOf
1820
import kotlinx.coroutines.test.runTest
1921
import org.junit.Rule
2022
import org.junit.Test
@@ -25,7 +27,7 @@ class CustomReactionPresenterTest {
2527

2628
private val presenter = CustomReactionPresenter(
2729
emojibaseProvider = FakeEmojibaseProvider(),
28-
getRecentEmojis = { Result.success(emptyList()) },
30+
getRecentEmojis = { Result.success(persistentListOf()) },
2931
)
3032

3133
@Test

features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/FakeEmojibaseProvider.kt

Lines changed: 0 additions & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)