Browse Source

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>
pull/1057/head
David Langley 1 year ago committed by GitHub
parent
commit
08a0f710d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt
  2. 4
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
  3. 18
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/EmojiPicker.kt
  4. 3
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionBottomSheet.kt
  5. 4
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionEvents.kt
  6. 10
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionPresenter.kt
  7. 2
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/customreaction/CustomReactionState.kt
  8. 20
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/CustomReactionPresenterTests.kt
  9. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_EmojiPickerDarkPreview_0_null,NEXUS_5,1.0,en].png
  10. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_EmojiPickerLightPreview_0_null,NEXUS_5,1.0,en].png

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

@ -30,6 +30,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData @@ -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( @@ -68,6 +69,7 @@ fun aMessagesState() = MessagesState(
customReactionState = CustomReactionState(
selectedEventId = null,
eventSink = {},
selectedEmoji = persistentSetOf(),
),
reactionSummaryState = ReactionSummaryState(
target = null,

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

@ -135,7 +135,7 @@ fun MessagesView( @@ -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( @@ -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,
)

18
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/EmojiPicker.kt

@ -17,6 +17,7 @@ @@ -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 @@ -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 @@ -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 @@ -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( @@ -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() { @@ -132,6 +145,7 @@ internal fun EmojiPickerDarkPreview() {
private fun ContentToPreview() {
EmojiPicker(
onEmojiSelected = {},
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
selectedEmojis = persistentSetOf("😀", "😄", "😃")
)
}

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

@ -57,7 +57,8 @@ fun CustomReactionBottomSheet( @@ -57,7 +57,8 @@ fun CustomReactionBottomSheet(
) {
EmojiPicker(
onEmojiSelected = ::onEmojiSelectedDismiss,
modifier = Modifier.fillMaxSize()
modifier = Modifier.fillMaxSize(),
selectedEmojis = state.selectedEmoji,
)
}
}

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

@ -16,8 +16,8 @@ @@ -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
}

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

@ -21,22 +21,24 @@ import androidx.compose.runtime.getValue @@ -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)
}
}

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

@ -17,8 +17,10 @@ @@ -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
features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/components/customreaction/CustomReactionPresenterTests.kt

@ -20,6 +20,8 @@ import app.cash.molecule.RecompositionMode @@ -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 { @@ -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)
}
}
}

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_EmojiPickerDarkPreview_0_null,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.timeline.components_null_DefaultGroup_EmojiPickerLightPreview_0_null,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.
Loading…
Cancel
Save