feat: server custom emoji into emote picker

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2023-10-15 01:45:05 +02:00
parent 6736a2677d
commit 5f82548d1e
4 changed files with 246 additions and 68 deletions

View File

@ -1,19 +1,25 @@
package chat.revolt.components.emoji package chat.revolt.components.emoji
import android.util.TypedValue
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
@ -21,23 +27,32 @@ import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import chat.revolt.R import chat.revolt.R
import chat.revolt.internals.EmojiCategory import chat.revolt.api.REVOLT_FILES
import chat.revolt.callbacks.Action
import chat.revolt.callbacks.ActionChannel
import chat.revolt.components.generic.IconPlaceholder
import chat.revolt.components.generic.RemoteImage
import chat.revolt.internals.Category
import chat.revolt.internals.EmojiMetadata import chat.revolt.internals.EmojiMetadata
import chat.revolt.internals.EmojiPickerItem import chat.revolt.internals.EmojiPickerItem
import chat.revolt.internals.UnicodeEmojiSection
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun EmojiPicker( fun EmojiPicker(
onEmojiSelected: (String) -> Unit, onEmojiSelected: (String) -> Unit,
@ -45,35 +60,56 @@ fun EmojiPicker(
val view = LocalView.current val view = LocalView.current
val metadata = remember { EmojiMetadata() } val metadata = remember { EmojiMetadata() }
val pickerList = remember(metadata) { metadata.flatPickerList() } val pickerList = remember(metadata) { metadata.flatPickerList() }
val servers = remember(metadata) { metadata.serversWithEmotes() }
val categorySpans = remember(pickerList) { metadata.categorySpans(pickerList) } val categorySpans = remember(pickerList) { metadata.categorySpans(pickerList) }
val gridState = rememberLazyGridState() val gridState = rememberLazyGridState()
val categoryRowScrollState = rememberScrollState()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val spanCount = 9 // https://github.com/googlefonts/emoji-metadata/#readme val spanCount = 9 // https://github.com/googlefonts/emoji-metadata/#readme
// The current category is the one that the user is currently looking at. // The current category is the one that the user is currently looking at.
// We calculate this using the grid state and the category spans val currentCategory = remember(gridState, categorySpans) {
// (which contain the start and end index of each category).
val currentCategory = remember {
derivedStateOf { derivedStateOf {
val firstVisibleItem = gridState.firstVisibleItemIndex val firstVisible = gridState.firstVisibleItemIndex
val firstCategory =
categorySpans.entries.firstOrNull { it.value.first <= firstVisible && it.value.second >= firstVisible }?.key
for (category in categorySpans.keys) { firstCategory
val (start, end) = categorySpans[category] ?: continue
if (firstVisibleItem + 1 in start..end) {
return@derivedStateOf category
}
}
return@derivedStateOf EmojiCategory.entries.last()
} }
} }
LaunchedEffect(currentCategory.value) {
// Scroll to the server icon of the current category.
val offset = categorySpans.entries.indexOfFirst { it.key == currentCategory.value }
var px = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
37f + 4f,
view.resources.displayMetrics
).toInt()
// If the user is looking at the unicode emoji, scroll to the end instead
// so that the category icons are all neatly aligned.
//
// Impl -> Not scrolling to "the end" but to the current category plus 50.
// (Which technically is an evil hack, but technically I could also
// poke an eye out with a spoon, so let's not worry about technicalities.)
if (currentCategory.value is Category.UnicodeEmojiCategory) px += 50
categoryRowScrollState.animateScrollTo(offset * px)
}
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
) { ) {
Row { Row(
EmojiCategory.entries.forEach { category -> modifier = Modifier
.horizontalScroll(categoryRowScrollState)
.height(37.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
servers.forEach { server ->
Column( Column(
modifier = Modifier modifier = Modifier
.clip(CircleShape) .clip(CircleShape)
@ -81,36 +117,79 @@ fun EmojiPicker(
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
scope.launch { scope.launch {
val index = val index =
pickerList.indexOfFirst { it is EmojiPickerItem.Category && it.category == category } pickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.ServerEmoteCategory && it.category.server == server }
gridState.animateScrollToItem(index) gridState.animateScrollToItem(index)
} }
} }
.then( .then(
if (currentCategory.value == category) { if (currentCategory.value is Category.ServerEmoteCategory && (currentCategory.value as Category.ServerEmoteCategory).server == server) {
Modifier.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.1f)) Modifier.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.1f))
} else { } else {
Modifier Modifier
} }
) )
.aspectRatio(1f) .aspectRatio(1f)
.weight(1f), .padding(4.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
if (server.icon == null) {
IconPlaceholder(
name = server.name ?: stringResource(R.string.unknown),
fontSize = 16.sp,
modifier = Modifier
.clip(CircleShape)
.fillMaxSize()
)
} else {
RemoteImage(
url = "$REVOLT_FILES/icons/${server.icon.id}/icon.gif?max_side=64",
description = server.name,
modifier = Modifier
.clip(CircleShape)
.fillMaxSize()
)
}
}
}
UnicodeEmojiSection.entries.forEach { category ->
Column(
modifier = Modifier
.clip(CircleShape)
.clickable {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
scope.launch {
val index =
pickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == category }
gridState.animateScrollToItem(index)
}
}
.then(
if (currentCategory.value is Category.UnicodeEmojiCategory && (currentCategory.value as Category.UnicodeEmojiCategory).definition == category) {
Modifier.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.1f))
} else {
Modifier
}
)
.aspectRatio(1f)
.padding(4.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
) { ) {
Icon( Icon(
painter = when (category) { painter = when (category) {
EmojiCategory.Smileys -> painterResource(R.drawable.ic_emoticon_24dp) UnicodeEmojiSection.Smileys -> painterResource(R.drawable.ic_emoticon_24dp)
EmojiCategory.People -> painterResource(R.drawable.ic_human_greeting_variant_24dp) UnicodeEmojiSection.People -> painterResource(R.drawable.ic_human_greeting_variant_24dp)
EmojiCategory.Animals -> painterResource(R.drawable.ic_snake_24dp) UnicodeEmojiSection.Animals -> painterResource(R.drawable.ic_snake_24dp)
EmojiCategory.Food -> painterResource(R.drawable.ic_glass_mug_variant_24dp) UnicodeEmojiSection.Food -> painterResource(R.drawable.ic_glass_mug_variant_24dp)
EmojiCategory.Travel -> painterResource(R.drawable.ic_train_bus_24dp) UnicodeEmojiSection.Travel -> painterResource(R.drawable.ic_train_bus_24dp)
EmojiCategory.Activities -> painterResource(R.drawable.ic_skate_24dp) UnicodeEmojiSection.Activities -> painterResource(R.drawable.ic_skate_24dp)
EmojiCategory.Objects -> painterResource(R.drawable.ic_table_chair_24dp) UnicodeEmojiSection.Objects -> painterResource(R.drawable.ic_table_chair_24dp)
EmojiCategory.Symbols -> painterResource(R.drawable.ic_symbol_24dp) UnicodeEmojiSection.Symbols -> painterResource(R.drawable.ic_symbol_24dp)
EmojiCategory.Flags -> painterResource(R.drawable.ic_flag_24dp) UnicodeEmojiSection.Flags -> painterResource(R.drawable.ic_flag_24dp)
}, },
contentDescription = null, contentDescription = null,
tint = if (currentCategory.value == category) { tint = if (currentCategory.value is Category.UnicodeEmojiCategory && (currentCategory.value as Category.UnicodeEmojiCategory).definition == category) {
MaterialTheme.colorScheme.primary MaterialTheme.colorScheme.primary
} else LocalContentColor.current } else LocalContentColor.current
) )
@ -122,26 +201,27 @@ fun EmojiPicker(
columns = GridCells.Fixed(spanCount), columns = GridCells.Fixed(spanCount),
horizontalArrangement = Arrangement.spacedBy(4.dp), horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.weight(1f) modifier = Modifier.fillMaxSize()
) { ) {
items( items(
pickerList.size, pickerList.size,
span = { span = {
val item = pickerList[it] val item = pickerList[it]
when (item) { when (item) {
is EmojiPickerItem.Emoji -> GridItemSpan(1) is EmojiPickerItem.UnicodeEmoji -> GridItemSpan(1)
is EmojiPickerItem.Category -> GridItemSpan(spanCount) is EmojiPickerItem.ServerEmote -> GridItemSpan(1)
is EmojiPickerItem.Section -> GridItemSpan(spanCount)
} }
} }
) { index -> ) { index ->
when (val item = pickerList[index]) { when (val item = pickerList[index]) {
is EmojiPickerItem.Emoji -> { is EmojiPickerItem.UnicodeEmoji -> {
Column( Column(
modifier = Modifier modifier = Modifier
.clip(CircleShape) .clip(CircleShape)
.clickable { .clickable {
onEmojiSelected(item.emoji)
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
onEmojiSelected(item.emoji)
} }
.aspectRatio(1f) .aspectRatio(1f)
.weight(1f), .weight(1f),
@ -152,13 +232,55 @@ fun EmojiPicker(
} }
} }
is EmojiPickerItem.Category -> { is EmojiPickerItem.ServerEmote -> {
Column(
modifier = Modifier
.clip(CircleShape)
.combinedClickable(
onClick = {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
onEmojiSelected(":${item.emote.id}:")
},
onLongClick = {
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
scope.launch {
item.emote.id?.let {
ActionChannel.send(
Action.EmoteInfo(
it
)
)
}
}
}
)
.aspectRatio(1f)
.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
RemoteImage(
url = "$REVOLT_FILES/emojis/${item.emote.id}/emoji.gif",
description = item.emote.name,
contentScale = ContentScale.Fit,
modifier = Modifier
.fillMaxSize()
.padding(8.dp)
)
}
}
is EmojiPickerItem.Section -> {
Text( Text(
stringResource(item.category.nameResource), when (item.category) {
is Category.UnicodeEmojiCategory -> stringResource(item.category.definition.nameResource)
is Category.ServerEmoteCategory -> item.category.server.name
?: stringResource(R.string.unknown)
},
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 8.dp) .padding(8.dp)
) )
} }
} }

View File

@ -11,23 +11,27 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
private val NoopHandler = {}
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun IconPlaceholder( fun IconPlaceholder(
name: String, name: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: () -> Unit = {}, onClick: () -> Unit = NoopHandler,
onLongClick: () -> Unit = {} onLongClick: () -> Unit = NoopHandler,
fontSize: TextUnit = 20.sp
) { ) {
Box( Box(
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
modifier = modifier modifier = modifier
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp)) .background(MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp))
.then( .then(
if (onClick != {} || onLongClick != {}) Modifier.combinedClickable( if (onClick != NoopHandler || onLongClick != NoopHandler) Modifier.combinedClickable(
onClick = onClick, onClick = onClick,
onLongClick = onLongClick onLongClick = onLongClick
) )
@ -36,7 +40,7 @@ fun IconPlaceholder(
) { ) {
Text( Text(
text = name.first().uppercase(), text = name.first().uppercase(),
fontSize = 20.sp, fontSize = fontSize,
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
color = MaterialTheme.colorScheme.onSurface color = MaterialTheme.colorScheme.onSurface
) )

View File

@ -3,7 +3,9 @@ package chat.revolt.internals
import android.content.Context import android.content.Context
import chat.revolt.R import chat.revolt.R
import chat.revolt.RevoltApplication import chat.revolt.RevoltApplication
import chat.revolt.api.RevoltAPI
import chat.revolt.api.RevoltJson import chat.revolt.api.RevoltJson
import chat.revolt.api.schemas.Server
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.ListSerializer
@ -22,7 +24,7 @@ data class EmojiGroup(
val emoji: List<Emoji>, val emoji: List<Emoji>,
) )
enum class EmojiCategory(val googleName: String, val nameResource: Int) { enum class UnicodeEmojiSection(val googleName: String, val nameResource: Int) {
Smileys("Smileys and emotions", R.string.emoji_category_smileys), Smileys("Smileys and emotions", R.string.emoji_category_smileys),
People("People", R.string.emoji_category_people), People("People", R.string.emoji_category_people),
Animals("Animals and nature", R.string.emoji_category_animals), Animals("Animals and nature", R.string.emoji_category_animals),
@ -34,9 +36,15 @@ enum class EmojiCategory(val googleName: String, val nameResource: Int) {
Flags("Flags", R.string.emoji_category_flags), Flags("Flags", R.string.emoji_category_flags),
} }
sealed class Category {
data class UnicodeEmojiCategory(val definition: UnicodeEmojiSection) : Category()
data class ServerEmoteCategory(val server: Server) : Category()
}
sealed class EmojiPickerItem { sealed class EmojiPickerItem {
data class Emoji(val emoji: String) : EmojiPickerItem() data class Section(val category: Category) : EmojiPickerItem()
data class Category(val category: EmojiCategory) : EmojiPickerItem() data class UnicodeEmoji(val emoji: String) : EmojiPickerItem()
data class ServerEmote(val emote: chat.revolt.api.schemas.Emoji) : EmojiPickerItem()
} }
class EmojiMetadata { class EmojiMetadata {
@ -49,25 +57,43 @@ class EmojiMetadata {
return RevoltJson.decodeFromString(ListSerializer(EmojiGroup.serializer()), json) return RevoltJson.decodeFromString(ListSerializer(EmojiGroup.serializer()), json)
} }
fun pickerList(): Map<EmojiCategory, List<Emoji>> { fun serversWithEmotes(): List<Server> {
val map = mutableMapOf<EmojiCategory, List<Emoji>>() return RevoltAPI
.emojiCache
.values
.asSequence()
.map { it.parent }
.filterNotNull()
.filter { it.type == "Server" }
.map { it.id }
.distinct()
.mapNotNull { RevoltAPI.serverCache[it] }
.toList()
}
for (group in metadata) { fun serverEmoteList(server: Server): List<EmojiPickerItem> {
val category = EmojiCategory.entries.find { it.name == group.group } ?: continue val list = mutableListOf<EmojiPickerItem>()
map[category] = group.emoji val emotes = RevoltAPI.emojiCache.values.filter { it.parent?.id == server.id }
}
return map list.add(EmojiPickerItem.Section(Category.ServerEmoteCategory(server)))
list.addAll(emotes.map { EmojiPickerItem.ServerEmote(it) })
return list
} }
fun flatPickerList(): List<EmojiPickerItem> { fun flatPickerList(): List<EmojiPickerItem> {
val list = mutableListOf<EmojiPickerItem>() val list = mutableListOf<EmojiPickerItem>()
for (server in serversWithEmotes()) {
list.addAll(serverEmoteList(server))
}
for (group in metadata) { for (group in metadata) {
val category = EmojiCategory.entries.find { it.googleName == group.group } ?: continue val category =
list.add(EmojiPickerItem.Category(category)) UnicodeEmojiSection.entries.find { it.googleName == group.group } ?: continue
list.add(EmojiPickerItem.Section(Category.UnicodeEmojiCategory(category)))
list.addAll(group.emoji.map { emoji -> list.addAll(group.emoji.map { emoji ->
EmojiPickerItem.Emoji( EmojiPickerItem.UnicodeEmoji(
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) } emoji.base.joinToString("") { String(Character.toChars(it.toInt())) }
) )
}) })
@ -77,25 +103,46 @@ class EmojiMetadata {
} }
/** /**
* Returns a map of category to start and end index of the category in the flat picker list. * Returns a map of category to start and end index of the category in the flat picker list
* Impl
* ====
* 1. Iterate through servers that have emotes. Get the index of the server emote category.
* 2. Get all emotes in that server. Add the size of that list to the index of the server emote category.
* 3. Push Pair(index, index + size) to the map.
* 4. Iterate through all unicode emoji categories. Get the index of the category.
* Unless it's the last category {
* 5.1. Get the index of the next category. Subtract 1 from that index.
* 5.2. Push Pair(index, lastIndex) to the map.
* } Otherwise {
* 5. Push Pair(index, Int.MAX_VALUE) to the map.
* }
* 6. Return the map.
*/ */
fun categorySpans(flatPickerList: List<EmojiPickerItem>): Map<EmojiCategory, Pair<Int, Int>> { fun categorySpans(flatPickerList: List<EmojiPickerItem>): Map<Category, Pair<Int, Int>> {
val map = mutableMapOf<EmojiCategory, Pair<Int, Int>>() val output = mutableMapOf<Category, Pair<Int, Int>>()
var start = 0
var end = 0
for (item in flatPickerList) { for (server in serversWithEmotes()) {
if (item is EmojiPickerItem.Category) { val index =
if (start != end) { flatPickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.ServerEmoteCategory && it.category.server == server }
map[flatPickerList[start].let { it as EmojiPickerItem.Category }.category] = val allEmotesInThatServer =
Pair(start, end) RevoltAPI.emojiCache.values.filter { it.parent?.id == server.id }
} val lastIndex = index + allEmotesInThatServer.size
start = end
output[Category.ServerEmoteCategory(server)] = Pair(index, lastIndex)
}
for (section in UnicodeEmojiSection.entries) {
val index =
flatPickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == section }
val lastIndex = if (section == UnicodeEmojiSection.entries.last()) {
Int.MAX_VALUE
} else {
val nextSection = UnicodeEmojiSection.entries[section.ordinal + 1]
flatPickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.UnicodeEmojiCategory && it.category.definition == nextSection } - 1
} }
end++ output[Category.UnicodeEmojiCategory(section)] = Pair(index, lastIndex)
} }
return map return output
} }
init { init {

View File

@ -6,6 +6,7 @@ import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.MediaStore import android.provider.MediaStore
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
@ -591,8 +592,12 @@ fun ChannelScreen(
) )
} }
} }
AnimatedVisibility(visible = viewModel.currentBottomPane == BottomPane.EmojiPicker) { AnimatedVisibility(visible = viewModel.currentBottomPane == BottomPane.EmojiPicker) {
BackHandler(enabled = viewModel.currentBottomPane == BottomPane.EmojiPicker) {
viewModel.currentBottomPane = BottomPane.None
}
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()