Use shared recent emoji reactions from account data (#5402)

* Use shared recent emoji reactions from account data

- Add `AddRecentEmoji` and `GetRecentEmojis` use cases to avoid injecting the whole `MatrixClient` for just one of these operations.
- Update the UI and logic of the emoji picker and message context menu to include the recent emojis.
- Add `CoroutineDispatchers.Default` with the defaults coroutines to use in the app for ease of use.

* Instead of replacing suggested emojis, concatenate recent ones removing duplicates

* Update screenshots

---------

Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
Jorge Martin Espinosa 2025-09-26 13:04:34 +02:00 committed by GitHub
parent 4dc65d9c08
commit f1cd80ede8
45 changed files with 572 additions and 208 deletions

View file

@ -8,9 +8,18 @@
package io.element.android.libraries.core.coroutine
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
data class CoroutineDispatchers(
val io: CoroutineDispatcher,
val computation: CoroutineDispatcher,
val main: CoroutineDispatcher,
)
) {
companion object {
val Default = CoroutineDispatchers(
io = Dispatchers.IO,
computation = Dispatchers.Default,
main = Dispatchers.Main,
)
}
}

View file

@ -173,6 +173,16 @@ interface MatrixClient {
* Returns the maximum file upload size allowed by the Matrix server.
*/
suspend fun getMaxFileUploadSize(): Result<Long>
/**
* Returns the list of shared recent emoji reactions for this account.
*/
suspend fun getRecentEmojis(): Result<List<String>>
/**
* Adds an emoji to the list of recent emoji reactions for this account.
*/
suspend fun addRecentEmoji(emoji: String): Result<Unit>
}
/**

View file

@ -0,0 +1,23 @@
/*
* 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.Inject
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.MatrixClient
import kotlinx.coroutines.withContext
@Inject
class AddRecentEmoji(
private val client: MatrixClient,
private val dispatchers: CoroutineDispatchers,
) {
suspend operator fun invoke(emoji: String): Result<Unit> = withContext(dispatchers.io) {
client.addRecentEmoji(emoji)
}
}

View file

@ -0,0 +1,30 @@
/*
* 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 dev.zacsweers.metro.Inject
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)
@Inject
class DefaultGetRecentEmojis(
private val client: MatrixClient,
private val dispatchers: CoroutineDispatchers,
) : GetRecentEmojis {
override suspend operator fun invoke(): Result<List<String>> = withContext(dispatchers.io) {
client.getRecentEmojis()
}
}

View file

@ -708,6 +708,18 @@ class RustMatrixClient(
runCatchingExceptions { innerClient.getMaxMediaUploadSize().toLong() }
}
override suspend fun addRecentEmoji(emoji: String): Result<Unit> = withContext(sessionDispatcher) {
runCatchingExceptions {
innerClient.addRecentEmoji(emoji)
}
}
override suspend fun getRecentEmojis(): Result<List<String>> = withContext(sessionDispatcher) {
runCatchingExceptions {
innerClient.getRecentEmojis().map { it.emoji }
}
}
private suspend fun File.getCacheSize(
includeCryptoDb: Boolean = false,
): Long = withContext(sessionDispatcher) {

View file

@ -97,6 +97,8 @@ class FakeMatrixClient(
override val ignoredUsersFlow: StateFlow<ImmutableList<UserId>> = MutableStateFlow(persistentListOf()),
private val getMaxUploadSizeResult: () -> Result<Long> = { lambdaError() },
private val getJoinedRoomIdsResult: () -> Result<Set<RoomId>> = { Result.success(emptySet()) },
private val getRecentEmojisLambda: () -> Result<List<String>> = { Result.success(emptyList()) },
private val addRecentEmojiLambda: (String) -> Result<Unit> = { Result.success(Unit) },
) : MatrixClient {
var setDisplayNameCalled: Boolean = false
private set
@ -349,4 +351,12 @@ class FakeMatrixClient(
override suspend fun getMaxFileUploadSize(): Result<Long> {
return getMaxUploadSizeResult()
}
override suspend fun addRecentEmoji(emoji: String): Result<Unit> {
return addRecentEmojiLambda(emoji)
}
override suspend fun getRecentEmojis(): Result<List<String>> {
return getRecentEmojisLambda()
}
}