Show selected reactions on the emoji picker. (#1014)
* Show selected reactions on the emoji picker. * Unused import * Update screenshots * Use ImmutableSet * Fix lint issues. --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
38a25dc3e9
commit
08a0f710d7
10 changed files with 55 additions and 16 deletions
|
|
@ -30,6 +30,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
|||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
|
||||
open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
|
||||
override val values: Sequence<MessagesState>
|
||||
|
|
@ -68,6 +69,7 @@ fun aMessagesState() = MessagesState(
|
|||
customReactionState = CustomReactionState(
|
||||
selectedEventId = null,
|
||||
eventSink = {},
|
||||
selectedEmoji = persistentSetOf(),
|
||||
),
|
||||
reactionSummaryState = ReactionSummaryState(
|
||||
target = null,
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ fun MessagesView(
|
|||
}
|
||||
|
||||
fun onMoreReactionsClicked(event: TimelineItem.Event) {
|
||||
state.customReactionState.eventSink(CustomReactionEvents.UpdateSelectedEvent(event.eventId))
|
||||
state.customReactionState.eventSink(CustomReactionEvents.UpdateSelectedEvent(event))
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
|
|
@ -187,7 +187,7 @@ fun MessagesView(
|
|||
state = state.actionListState,
|
||||
onActionSelected = ::onActionSelected,
|
||||
onCustomReactionClicked = { event ->
|
||||
state.customReactionState.eventSink(CustomReactionEvents.UpdateSelectedEvent(event.eventId))
|
||||
state.customReactionState.eventSink(CustomReactionEvents.UpdateSelectedEvent(event))
|
||||
},
|
||||
onEmojiReactionClicked = ::onEmojiReactionClicked,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package io.element.android.features.messages.impl.timeline.components
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -31,6 +32,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
|||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
|
|
@ -39,6 +41,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vanniktech.emoji.Emoji
|
||||
|
|
@ -48,12 +51,15 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
|||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun EmojiPicker(
|
||||
onEmojiSelected: (Emoji) -> Unit,
|
||||
selectedEmojis: ImmutableSet<String>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
|
@ -91,12 +97,19 @@ fun EmojiPicker(
|
|||
modifier = Modifier.fillMaxSize(),
|
||||
columns = GridCells.Adaptive(minSize = 40.dp),
|
||||
contentPadding = PaddingValues(vertical = 10.dp, horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
items(category.emojis, key = { it.unicode }) { item ->
|
||||
val backgroundColor = if (selectedEmojis.contains(item.unicode)) {
|
||||
ElementTheme.colors.bgActionPrimaryRest
|
||||
} else {
|
||||
Color.Transparent
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.background(backgroundColor, CircleShape)
|
||||
.clickable(
|
||||
enabled = true,
|
||||
onClick = { onEmojiSelected(item) },
|
||||
|
|
@ -132,6 +145,7 @@ internal fun EmojiPickerDarkPreview() {
|
|||
private fun ContentToPreview() {
|
||||
EmojiPicker(
|
||||
onEmojiSelected = {},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
selectedEmojis = persistentSetOf("😀", "😄", "😃")
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,8 @@ fun CustomReactionBottomSheet(
|
|||
) {
|
||||
EmojiPicker(
|
||||
onEmojiSelected = ::onEmojiSelectedDismiss,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
selectedEmojis = state.selectedEmoji,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@
|
|||
|
||||
package io.element.android.features.messages.impl.timeline.components.customreaction
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
|
||||
sealed interface CustomReactionEvents {
|
||||
data class UpdateSelectedEvent(val eventId: EventId?) : CustomReactionEvents
|
||||
data class UpdateSelectedEvent(val event: TimelineItem.Event?) : CustomReactionEvents
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,22 +21,24 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import javax.inject.Inject
|
||||
|
||||
class CustomReactionPresenter @Inject constructor() : Presenter<CustomReactionState> {
|
||||
|
||||
@Composable
|
||||
override fun present(): CustomReactionState {
|
||||
var selectedEventId by remember { mutableStateOf<EventId?>(null) }
|
||||
var selectedEvent by remember { mutableStateOf<TimelineItem.Event?>(null) }
|
||||
|
||||
fun handleEvents(event: CustomReactionEvents) {
|
||||
when (event) {
|
||||
is CustomReactionEvents.UpdateSelectedEvent -> selectedEventId = event.eventId
|
||||
is CustomReactionEvents.UpdateSelectedEvent -> selectedEvent = event.event
|
||||
}
|
||||
}
|
||||
|
||||
return CustomReactionState(selectedEventId = selectedEventId, eventSink = ::handleEvents)
|
||||
val selectedEmoji = selectedEvent?.reactionsState?.reactions?.mapNotNull { if(it.isHighlighted) it.key else null }.orEmpty().toImmutableSet()
|
||||
return CustomReactionState(selectedEventId = selectedEvent?.eventId, selectedEmoji = selectedEmoji, eventSink = ::handleEvents)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@
|
|||
package io.element.android.features.messages.impl.timeline.components.customreaction
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
|
||||
data class CustomReactionState(
|
||||
val selectedEventId: EventId?,
|
||||
val selectedEmoji: ImmutableSet<String>,
|
||||
val eventSink: (CustomReactionEvents) -> Unit,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import app.cash.molecule.RecompositionMode
|
|||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
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.features.messages.impl.timeline.components.customreaction.CustomReactionEvents
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
|
|
@ -38,11 +40,27 @@ class CustomReactionPresenterTests {
|
|||
val initialState = awaitItem()
|
||||
assertThat(initialState.selectedEventId).isNull()
|
||||
|
||||
initialState.eventSink(CustomReactionEvents.UpdateSelectedEvent(AN_EVENT_ID))
|
||||
initialState.eventSink(CustomReactionEvents.UpdateSelectedEvent(aTimelineItemEvent(eventId = AN_EVENT_ID)))
|
||||
assertThat(awaitItem().selectedEventId).isEqualTo(AN_EVENT_ID)
|
||||
|
||||
initialState.eventSink(CustomReactionEvents.UpdateSelectedEvent(null))
|
||||
assertThat(awaitItem().selectedEventId).isNull()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle selected emojis`() = runTest {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.selectedEventId).isNull()
|
||||
val reactions = aTimelineItemReactions(count = 1, isHighlighted = true)
|
||||
val key = reactions.reactions.first().key
|
||||
initialState.eventSink(CustomReactionEvents.UpdateSelectedEvent(aTimelineItemEvent(eventId = AN_EVENT_ID, timelineItemReactions = reactions)))
|
||||
val stateWithSelectedEmojis = awaitItem()
|
||||
assertThat(stateWithSelectedEmojis.selectedEventId).isEqualTo(AN_EVENT_ID)
|
||||
assertThat(stateWithSelectedEmojis.selectedEmoji).contains(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dedb3940216fd18804157f708340a535008cc753a579ced3ac4153f99bf01218
|
||||
size 175737
|
||||
oid sha256:c5650ec2689d4ce0cf0785e12d5cb899c1bdfdad14c1f75106be1068df662f53
|
||||
size 188815
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:19d8a7e0d477cc7db1bea85407b71516347b158777db2e3889734f2129b69de3
|
||||
size 175762
|
||||
oid sha256:2e5e11d9c1e7d4b950a0d080e8104f08b26fac56332d243502430dbfdd02c60b
|
||||
size 188259
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue