Migrate EmojiPickerPresenter to assisted-factory pattern with EmptyGetRecentEmojis

This commit is contained in:
ganfra
2026-07-22 15:31:40 +02:00
parent 5d8290b240
commit 93d6d5c84c
7 changed files with 38 additions and 15 deletions
@@ -37,7 +37,6 @@ fun CustomReactionBottomSheet(
CustomReactionState.Target.None, CustomReactionState.Target.None,
is CustomReactionState.Target.Loading -> Unit is CustomReactionState.Target.Loading -> Unit
is CustomReactionState.Target.Success -> { is CustomReactionState.Target.Success -> {
fun onEmojiSelectedDismiss(emoji: Emoji) { fun onEmojiSelectedDismiss(emoji: Emoji) {
sheetState.hide(coroutineScope) { sheetState.hide(coroutineScope) {
state.eventSink(CustomReactionEvent.DismissCustomReactionSheet) state.eventSink(CustomReactionEvent.DismissCustomReactionSheet)
@@ -49,7 +48,6 @@ fun CustomReactionBottomSheet(
state.eventSink(CustomReactionEvent.DismissCustomReactionSheet) state.eventSink(CustomReactionEvent.DismissCustomReactionSheet)
} }
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = ::onDismiss, onDismissRequest = ::onDismiss,
sheetState = sheetState, sheetState = sheetState,
@@ -17,12 +17,15 @@ 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.emoji.api.picker.EmojiPickerPresenter import io.element.android.libraries.emoji.api.picker.EmojiPickerPresenter
import io.element.android.libraries.emoji.api.recentemojis.GetRecentEmojis
import kotlinx.collections.immutable.toImmutableSet import kotlinx.collections.immutable.toImmutableSet
@Inject @Inject
class CustomReactionPresenter( class CustomReactionPresenter(
private val emojiPickerPresenter: EmojiPickerPresenter, emojiPickerPresenterFactory: EmojiPickerPresenter.Factory,
getRecentEmojis: GetRecentEmojis,
) : Presenter<CustomReactionState> { ) : Presenter<CustomReactionState> {
private val emojiPickerPresenter = emojiPickerPresenterFactory.create(getRecentEmojis)
@Composable @Composable
override fun present(): CustomReactionState { override fun present(): CustomReactionState {
var internalTarget by remember { mutableStateOf<InternalTarget>(InternalTarget.None) } var internalTarget by remember { mutableStateOf<InternalTarget>(InternalTarget.None) }
@@ -11,6 +11,8 @@ package io.element.android.features.messages.impl.timeline.components.customreac
import com.google.common.truth.Truth.assertThat 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.emoji.api.picker.EmojiPickerPresenter
import io.element.android.libraries.emoji.api.recentemojis.EmptyGetRecentEmojis
import io.element.android.libraries.emoji.impl.picker.anEmojiPickerState import io.element.android.libraries.emoji.impl.picker.anEmojiPickerState
import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
@@ -24,7 +26,8 @@ class CustomReactionPresenterTest {
val warmUpRule = WarmUpRule() val warmUpRule = WarmUpRule()
private val presenter = CustomReactionPresenter( private val presenter = CustomReactionPresenter(
emojiPickerPresenter = { anEmojiPickerState() }, emojiPickerPresenterFactory = EmojiPickerPresenter.Factory { EmojiPickerPresenter { anEmojiPickerState() } },
getRecentEmojis = EmptyGetRecentEmojis,
) )
@Test @Test
@@ -36,8 +39,6 @@ class CustomReactionPresenterTest {
initialState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event)) initialState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event))
assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.Loading(event))
val eventId = (awaitItem().target as? CustomReactionState.Target.Success)?.event?.eventId val eventId = (awaitItem().target as? CustomReactionState.Target.Success)?.event?.eventId
assertThat(eventId).isEqualTo(AN_EVENT_ID) assertThat(eventId).isEqualTo(AN_EVENT_ID)
@@ -57,8 +58,6 @@ class CustomReactionPresenterTest {
val key = reactions.reactions.first().key val key = reactions.reactions.first().key
initialState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event)) initialState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event))
assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.Loading(event))
val stateWithSelectedEmojis = awaitItem() val stateWithSelectedEmojis = awaitItem()
val eventId = (stateWithSelectedEmojis.target as? CustomReactionState.Target.Success)?.event?.eventId val eventId = (stateWithSelectedEmojis.target as? CustomReactionState.Target.Success)?.event?.eventId
assertThat(eventId).isEqualTo(AN_EVENT_ID) assertThat(eventId).isEqualTo(AN_EVENT_ID)
+1 -1
View File
@@ -18,5 +18,5 @@ dependencies {
implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrix.api)
implementation(libs.kotlinx.collections.immutable) implementation(libs.kotlinx.collections.immutable)
implementation(libs.matrix.emojibase.bindings) api(libs.matrix.emojibase.bindings)
} }
@@ -8,5 +8,10 @@
package io.element.android.libraries.emoji.api.picker package io.element.android.libraries.emoji.api.picker
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.emoji.api.recentemojis.GetRecentEmojis
fun interface EmojiPickerPresenter : Presenter<EmojiPickerState> fun interface EmojiPickerPresenter : Presenter<EmojiPickerState> {
fun interface Factory {
fun create(getRecentEmojis: GetRecentEmojis): EmojiPickerPresenter
}
}
@@ -9,6 +9,7 @@
package io.element.android.libraries.emoji.api.recentemojis package io.element.android.libraries.emoji.api.recentemojis
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
/** /**
* Returns the list of recently used emojis for reactions. * Returns the list of recently used emojis for reactions.
@@ -16,3 +17,11 @@ import kotlinx.collections.immutable.ImmutableList
fun interface GetRecentEmojis { fun interface GetRecentEmojis {
suspend operator fun invoke(): Result<ImmutableList<String>> suspend operator fun invoke(): Result<ImmutableList<String>>
} }
/**
* A [GetRecentEmojis] that never returns any recent emojis. Handy for pickers that don't want
* a "Recent" tab (e.g. the user-status picker).
*/
object EmptyGetRecentEmojis : GetRecentEmojis {
override suspend fun invoke(): Result<ImmutableList<String>> = Result.success(persistentListOf())
}
@@ -17,8 +17,10 @@ import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.platform.LocalInspectionMode
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject
import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.Inject
import io.element.android.emojibasebindings.Emoji import io.element.android.emojibasebindings.Emoji
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.designsystem.theme.components.IconSource import io.element.android.libraries.designsystem.theme.components.IconSource
@@ -37,13 +39,18 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
@Inject @AssistedInject
@ContributesBinding(SessionScope::class)
class DefaultEmojiPickerPresenter( class DefaultEmojiPickerPresenter(
private val emojibaseProvider: EmojibaseProvider, private val emojibaseProvider: EmojibaseProvider,
private val getRecentEmojis: GetRecentEmojis, @Assisted private val getRecentEmojis: GetRecentEmojis,
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
) : EmojiPickerPresenter { ) : EmojiPickerPresenter {
@AssistedFactory
@ContributesBinding(SessionScope::class)
interface Factory : EmojiPickerPresenter.Factory {
override fun create(getRecentEmojis: GetRecentEmojis): DefaultEmojiPickerPresenter
}
@Composable @Composable
override fun present(): EmojiPickerState { override fun present(): EmojiPickerState {
val queryState = rememberTextFieldState() val queryState = rememberTextFieldState()
@@ -86,7 +93,9 @@ class DefaultEmojiPickerPresenter(
val searchQuery = queryState.text.toString() val searchQuery = queryState.text.toString()
LaunchedEffect(searchQuery, data) { LaunchedEffect(searchQuery, data) {
if (searchQuery.isEmpty() || data.allEmojis.isEmpty()) { if (searchQuery.isEmpty() || data.allEmojis.isEmpty()) {
emojiResults = SearchBarResultState.Initial() if (emojiResults !is SearchBarResultState.Initial) {
emojiResults = SearchBarResultState.Initial()
}
return@LaunchedEffect return@LaunchedEffect
} }
delay(100.milliseconds) delay(100.milliseconds)