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:
committed by
GitHub
parent
7facc40771
commit
45b5783b23
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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())
|
|
||||||
}
|
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
26
libraries/recentemojis/api/build.gradle.kts
Normal file
26
libraries/recentemojis/api/build.gradle.kts
Normal 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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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>>
|
||||||
|
}
|
||||||
35
libraries/recentemojis/impl/build.gradle.kts
Normal file
35
libraries/recentemojis/impl/build.gradle.kts
Normal 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)
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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),
|
||||||
|
)
|
||||||
|
}
|
||||||
25
libraries/recentemojis/test/build.gradle.kts
Normal file
25
libraries/recentemojis/test/build.gradle.kts
Normal 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)
|
||||||
|
}
|
||||||
@@ -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())
|
||||||
|
}
|
||||||
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user