Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Extremely WIP] Emoji reaction skin tone selection #3342

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ fun aCustomReactionState(
) = CustomReactionState(
target = target,
selectedEmoji = persistentSetOf(),
skinTone = null,
eventSink = eventSink,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,8 @@ fun MessagesView(
CustomReactionBottomSheet(
state = state.customReactionState,
onSelectEmoji = { eventId, emoji ->
state.eventSink(MessagesEvents.ToggleReaction(emoji.unicode, eventId))
}
state.eventSink(MessagesEvents.ToggleReaction(emoji, eventId))
},
)

ReactionSummaryView(state = state.reactionSummaryState)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import io.element.android.emojibasebindings.Emoji
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet
import io.element.android.libraries.designsystem.theme.components.hide
import io.element.android.libraries.matrix.api.core.EventId
Expand All @@ -31,7 +30,7 @@ import io.element.android.libraries.matrix.api.core.EventId
@Composable
fun CustomReactionBottomSheet(
state: CustomReactionState,
onSelectEmoji: (EventId, Emoji) -> Unit,
onSelectEmoji: (EventId, String) -> Unit,
modifier: Modifier = Modifier,
) {
val sheetState = rememberModalBottomSheetState()
Expand All @@ -42,7 +41,7 @@ fun CustomReactionBottomSheet(
state.eventSink(CustomReactionEvents.DismissCustomReactionSheet)
}

fun onEmojiSelectedDismiss(emoji: Emoji) {
fun onEmojiSelectedDismiss(emoji: String) {
if (target?.event?.eventId == null) return
sheetState.hide(coroutineScope) {
state.eventSink(CustomReactionEvents.DismissCustomReactionSheet)
Expand All @@ -60,6 +59,7 @@ fun CustomReactionBottomSheet(
onSelectEmoji = ::onEmojiSelectedDismiss,
emojibaseStore = target.emojibaseStore,
selectedEmojis = state.selectedEmoji,
skinTone = state.skinTone,
modifier = Modifier.fillMaxSize(),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,28 @@ package io.element.android.features.messages.impl.timeline.components.customreac

import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.launch
import javax.inject.Inject

class CustomReactionPresenter @Inject constructor(
private val emojibaseProvider: EmojibaseProvider
private val emojibaseProvider: EmojibaseProvider,
private val sessionPreferencesStore: SessionPreferencesStore,
) : Presenter<CustomReactionState> {
@Composable
override fun present(): CustomReactionState {
val target: MutableState<CustomReactionState.Target> = remember {
mutableStateOf(CustomReactionState.Target.None)
}
val skinTone by sessionPreferencesStore.getSkinTone().collectAsState(initial = null)

val localCoroutineScope = rememberCoroutineScope()
fun handleShowCustomReactionSheet(event: TimelineItem.Event) {
Expand Down Expand Up @@ -67,7 +72,8 @@ class CustomReactionPresenter @Inject constructor(
return CustomReactionState(
target = target.value,
selectedEmoji = selectedEmoji,
eventSink = { handleEvents(it) }
skinTone = skinTone,
eventSink = ::handleEvents,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import kotlinx.collections.immutable.ImmutableSet
data class CustomReactionState(
val target: Target,
val selectedEmoji: ImmutableSet<String>,
val skinTone: String?,
val eventSink: (CustomReactionEvents) -> Unit,
) {
sealed interface Target {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,40 +16,63 @@

package io.element.android.features.messages.impl.timeline.components.customreaction

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.element.android.compound.theme.ElementTheme
import io.element.android.emojibasebindings.Emoji
import io.element.android.emojibasebindings.EmojiSkin
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.toDp
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.coroutines.launch

val SKIN_MODIFIERS = setOf("🏻", "🏼", "🏽", "🏾", "🏿")

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun EmojiItem(
item: Emoji,
emoji: String,
isSelected: Boolean,
onSelectEmoji: (Emoji) -> Unit,
onSelectEmoji: (String) -> Unit,
onLongPress: () -> Unit,
modifier: Modifier = Modifier,
emojiSize: TextUnit = 20.sp,
) {
Expand All @@ -59,17 +82,18 @@ fun EmojiItem(
Color.Transparent
}
val description = if (isSelected) {
stringResource(id = CommonStrings.a11y_remove_reaction_with, item.unicode)
stringResource(id = CommonStrings.a11y_remove_reaction_with, emoji)
} else {
stringResource(id = CommonStrings.a11y_react_with, item.unicode)
stringResource(id = CommonStrings.a11y_react_with, emoji)
}
Box(
modifier = modifier
.sizeIn(minWidth = 40.dp, minHeight = 40.dp)
.background(backgroundColor, CircleShape)
.clickable(
.combinedClickable(
enabled = true,
onClick = { onSelectEmoji(item) },
onClick = { onSelectEmoji(emoji) },
onLongClick = onLongPress,
indication = rememberRipple(bounded = false, radius = emojiSize.toDp() / 2 + 10.dp),
interactionSource = remember { MutableInteractionSource() }
)
Expand All @@ -79,31 +103,29 @@ fun EmojiItem(
contentAlignment = Alignment.Center
) {
Text(
text = item.unicode,
text = emoji,
style = LocalTextStyle.current.copy(fontSize = emojiSize),
)
}
}

@PreviewsDayNight
@Composable
internal fun EmojiItemPreview() = ElementPreview {
internal fun EmojiItemPreview(@PreviewParameter(EmojiItemVariant::class) variant: String) = ElementPreview {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
for (isSelected in listOf(true, false)) {
EmojiItem(
item = Emoji(
hexcode = "",
label = "",
tags = null,
shortcodes = emptyList(),
unicode = "👍",
skins = null
),
emoji = "👍$variant",
isSelected = isSelected,
onSelectEmoji = {},
onLongPress = {},
)
}
}
}

internal class EmojiItemVariant : PreviewParameterProvider<String> {
override val values = sequenceOf("") + SKIN_MODIFIERS.asSequence()
}
Loading
Loading