feat: server custom emoji into emote picker
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
6736a2677d
commit
5f82548d1e
|
|
@ -1,19 +1,25 @@
|
|||
package chat.revolt.components.emoji
|
||||
|
||||
import android.util.TypedValue
|
||||
import android.view.HapticFeedbackConstants
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
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.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
|
|
@ -21,23 +27,32 @@ import androidx.compose.material3.LocalTextStyle
|
|||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
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.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
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.EmojiPickerItem
|
||||
import chat.revolt.internals.UnicodeEmojiSection
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun EmojiPicker(
|
||||
onEmojiSelected: (String) -> Unit,
|
||||
|
|
@ -45,35 +60,56 @@ fun EmojiPicker(
|
|||
val view = LocalView.current
|
||||
val metadata = remember { EmojiMetadata() }
|
||||
val pickerList = remember(metadata) { metadata.flatPickerList() }
|
||||
val servers = remember(metadata) { metadata.serversWithEmotes() }
|
||||
val categorySpans = remember(pickerList) { metadata.categorySpans(pickerList) }
|
||||
val gridState = rememberLazyGridState()
|
||||
val categoryRowScrollState = rememberScrollState()
|
||||
val scope = rememberCoroutineScope()
|
||||
val spanCount = 9 // https://github.com/googlefonts/emoji-metadata/#readme
|
||||
|
||||
// The current category is the one that the user is currently looking at.
|
||||
// We calculate this using the grid state and the category spans
|
||||
// (which contain the start and end index of each category).
|
||||
val currentCategory = remember {
|
||||
val currentCategory = remember(gridState, categorySpans) {
|
||||
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) {
|
||||
val (start, end) = categorySpans[category] ?: continue
|
||||
if (firstVisibleItem + 1 in start..end) {
|
||||
return@derivedStateOf category
|
||||
}
|
||||
}
|
||||
|
||||
return@derivedStateOf EmojiCategory.entries.last()
|
||||
firstCategory
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
Row {
|
||||
EmojiCategory.entries.forEach { category ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.horizontalScroll(categoryRowScrollState)
|
||||
.height(37.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
servers.forEach { server ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
|
|
@ -81,36 +117,79 @@ fun EmojiPicker(
|
|||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
scope.launch {
|
||||
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)
|
||||
}
|
||||
}
|
||||
.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))
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
.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,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Icon(
|
||||
painter = when (category) {
|
||||
EmojiCategory.Smileys -> painterResource(R.drawable.ic_emoticon_24dp)
|
||||
EmojiCategory.People -> painterResource(R.drawable.ic_human_greeting_variant_24dp)
|
||||
EmojiCategory.Animals -> painterResource(R.drawable.ic_snake_24dp)
|
||||
EmojiCategory.Food -> painterResource(R.drawable.ic_glass_mug_variant_24dp)
|
||||
EmojiCategory.Travel -> painterResource(R.drawable.ic_train_bus_24dp)
|
||||
EmojiCategory.Activities -> painterResource(R.drawable.ic_skate_24dp)
|
||||
EmojiCategory.Objects -> painterResource(R.drawable.ic_table_chair_24dp)
|
||||
EmojiCategory.Symbols -> painterResource(R.drawable.ic_symbol_24dp)
|
||||
EmojiCategory.Flags -> painterResource(R.drawable.ic_flag_24dp)
|
||||
UnicodeEmojiSection.Smileys -> painterResource(R.drawable.ic_emoticon_24dp)
|
||||
UnicodeEmojiSection.People -> painterResource(R.drawable.ic_human_greeting_variant_24dp)
|
||||
UnicodeEmojiSection.Animals -> painterResource(R.drawable.ic_snake_24dp)
|
||||
UnicodeEmojiSection.Food -> painterResource(R.drawable.ic_glass_mug_variant_24dp)
|
||||
UnicodeEmojiSection.Travel -> painterResource(R.drawable.ic_train_bus_24dp)
|
||||
UnicodeEmojiSection.Activities -> painterResource(R.drawable.ic_skate_24dp)
|
||||
UnicodeEmojiSection.Objects -> painterResource(R.drawable.ic_table_chair_24dp)
|
||||
UnicodeEmojiSection.Symbols -> painterResource(R.drawable.ic_symbol_24dp)
|
||||
UnicodeEmojiSection.Flags -> painterResource(R.drawable.ic_flag_24dp)
|
||||
},
|
||||
contentDescription = null,
|
||||
tint = if (currentCategory.value == category) {
|
||||
tint = if (currentCategory.value is Category.UnicodeEmojiCategory && (currentCategory.value as Category.UnicodeEmojiCategory).definition == category) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else LocalContentColor.current
|
||||
)
|
||||
|
|
@ -122,26 +201,27 @@ fun EmojiPicker(
|
|||
columns = GridCells.Fixed(spanCount),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.weight(1f)
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
items(
|
||||
pickerList.size,
|
||||
span = {
|
||||
val item = pickerList[it]
|
||||
when (item) {
|
||||
is EmojiPickerItem.Emoji -> GridItemSpan(1)
|
||||
is EmojiPickerItem.Category -> GridItemSpan(spanCount)
|
||||
is EmojiPickerItem.UnicodeEmoji -> GridItemSpan(1)
|
||||
is EmojiPickerItem.ServerEmote -> GridItemSpan(1)
|
||||
is EmojiPickerItem.Section -> GridItemSpan(spanCount)
|
||||
}
|
||||
}
|
||||
) { index ->
|
||||
when (val item = pickerList[index]) {
|
||||
is EmojiPickerItem.Emoji -> {
|
||||
is EmojiPickerItem.UnicodeEmoji -> {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.clip(CircleShape)
|
||||
.clickable {
|
||||
onEmojiSelected(item.emoji)
|
||||
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||
onEmojiSelected(item.emoji)
|
||||
}
|
||||
.aspectRatio(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(
|
||||
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,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp, vertical = 8.dp)
|
||||
.padding(8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,23 +11,27 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
private val NoopHandler = {}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun IconPlaceholder(
|
||||
name: String,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit = {},
|
||||
onLongClick: () -> Unit = {}
|
||||
onClick: () -> Unit = NoopHandler,
|
||||
onLongClick: () -> Unit = NoopHandler,
|
||||
fontSize: TextUnit = 20.sp
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier
|
||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp))
|
||||
.then(
|
||||
if (onClick != {} || onLongClick != {}) Modifier.combinedClickable(
|
||||
if (onClick != NoopHandler || onLongClick != NoopHandler) Modifier.combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
)
|
||||
|
|
@ -36,7 +40,7 @@ fun IconPlaceholder(
|
|||
) {
|
||||
Text(
|
||||
text = name.first().uppercase(),
|
||||
fontSize = 20.sp,
|
||||
fontSize = fontSize,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ package chat.revolt.internals
|
|||
import android.content.Context
|
||||
import chat.revolt.R
|
||||
import chat.revolt.RevoltApplication
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.RevoltJson
|
||||
import chat.revolt.api.schemas.Server
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
|
||||
|
|
@ -22,7 +24,7 @@ data class EmojiGroup(
|
|||
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),
|
||||
People("People", R.string.emoji_category_people),
|
||||
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),
|
||||
}
|
||||
|
||||
sealed class Category {
|
||||
data class UnicodeEmojiCategory(val definition: UnicodeEmojiSection) : Category()
|
||||
data class ServerEmoteCategory(val server: Server) : Category()
|
||||
}
|
||||
|
||||
sealed class EmojiPickerItem {
|
||||
data class Emoji(val emoji: String) : EmojiPickerItem()
|
||||
data class Category(val category: EmojiCategory) : EmojiPickerItem()
|
||||
data class Section(val category: Category) : EmojiPickerItem()
|
||||
data class UnicodeEmoji(val emoji: String) : EmojiPickerItem()
|
||||
data class ServerEmote(val emote: chat.revolt.api.schemas.Emoji) : EmojiPickerItem()
|
||||
}
|
||||
|
||||
class EmojiMetadata {
|
||||
|
|
@ -49,25 +57,43 @@ class EmojiMetadata {
|
|||
return RevoltJson.decodeFromString(ListSerializer(EmojiGroup.serializer()), json)
|
||||
}
|
||||
|
||||
fun pickerList(): Map<EmojiCategory, List<Emoji>> {
|
||||
val map = mutableMapOf<EmojiCategory, List<Emoji>>()
|
||||
fun serversWithEmotes(): List<Server> {
|
||||
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) {
|
||||
val category = EmojiCategory.entries.find { it.name == group.group } ?: continue
|
||||
map[category] = group.emoji
|
||||
}
|
||||
fun serverEmoteList(server: Server): List<EmojiPickerItem> {
|
||||
val list = mutableListOf<EmojiPickerItem>()
|
||||
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> {
|
||||
val list = mutableListOf<EmojiPickerItem>()
|
||||
|
||||
for (server in serversWithEmotes()) {
|
||||
list.addAll(serverEmoteList(server))
|
||||
}
|
||||
|
||||
for (group in metadata) {
|
||||
val category = EmojiCategory.entries.find { it.googleName == group.group } ?: continue
|
||||
list.add(EmojiPickerItem.Category(category))
|
||||
val category =
|
||||
UnicodeEmojiSection.entries.find { it.googleName == group.group } ?: continue
|
||||
list.add(EmojiPickerItem.Section(Category.UnicodeEmojiCategory(category)))
|
||||
list.addAll(group.emoji.map { emoji ->
|
||||
EmojiPickerItem.Emoji(
|
||||
EmojiPickerItem.UnicodeEmoji(
|
||||
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>> {
|
||||
val map = mutableMapOf<EmojiCategory, Pair<Int, Int>>()
|
||||
var start = 0
|
||||
var end = 0
|
||||
fun categorySpans(flatPickerList: List<EmojiPickerItem>): Map<Category, Pair<Int, Int>> {
|
||||
val output = mutableMapOf<Category, Pair<Int, Int>>()
|
||||
|
||||
for (item in flatPickerList) {
|
||||
if (item is EmojiPickerItem.Category) {
|
||||
if (start != end) {
|
||||
map[flatPickerList[start].let { it as EmojiPickerItem.Category }.category] =
|
||||
Pair(start, end)
|
||||
}
|
||||
start = end
|
||||
for (server in serversWithEmotes()) {
|
||||
val index =
|
||||
flatPickerList.indexOfFirst { it is EmojiPickerItem.Section && it.category is Category.ServerEmoteCategory && it.category.server == server }
|
||||
val allEmotesInThatServer =
|
||||
RevoltAPI.emojiCache.values.filter { it.parent?.id == server.id }
|
||||
val lastIndex = index + allEmotesInThatServer.size
|
||||
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import android.os.Build
|
|||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
|
|
@ -591,8 +592,12 @@ fun ChannelScreen(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AnimatedVisibility(visible = viewModel.currentBottomPane == BottomPane.EmojiPicker) {
|
||||
BackHandler(enabled = viewModel.currentBottomPane == BottomPane.EmojiPicker) {
|
||||
viewModel.currentBottomPane = BottomPane.None
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
|
|||
Loading…
Reference in New Issue