feat: category list for unicode emoji

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2023-10-14 19:05:26 +02:00
parent c4f2d10cb5
commit 6736a2677d
10 changed files with 168 additions and 3 deletions

View File

@ -1,30 +1,42 @@
package chat.revolt.components.emoji
import android.view.HapticFeedbackConstants
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
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.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.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.revolt.R
import chat.revolt.internals.EmojiCategory
import chat.revolt.internals.EmojiMetadata
import chat.revolt.internals.EmojiPickerItem
import kotlinx.coroutines.launch
@Composable
fun EmojiPicker(
@ -33,16 +45,80 @@ fun EmojiPicker(
val view = LocalView.current
val metadata = remember { EmojiMetadata() }
val pickerList = remember(metadata) { metadata.flatPickerList() }
val categorySpans = remember(pickerList) { metadata.categorySpans(pickerList) }
val gridState = rememberLazyGridState()
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 {
derivedStateOf {
val firstVisibleItem = gridState.firstVisibleItemIndex
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()
}
}
Column(
modifier = Modifier
.fillMaxSize()
) {
Row {
Text("Categories", fontWeight = FontWeight.Black)
EmojiCategory.entries.forEach { category ->
Column(
modifier = Modifier
.clip(CircleShape)
.clickable {
view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
scope.launch {
val index =
pickerList.indexOfFirst { it is EmojiPickerItem.Category && it.category == category }
gridState.animateScrollToItem(index)
}
}
.then(
if (currentCategory.value == category) {
Modifier.background(MaterialTheme.colorScheme.primary.copy(alpha = 0.1f))
} else {
Modifier
}
)
.aspectRatio(1f)
.weight(1f),
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)
},
contentDescription = null,
tint = if (currentCategory.value == category) {
MaterialTheme.colorScheme.primary
} else LocalContentColor.current
)
}
}
}
LazyVerticalGrid(
state = gridState,
columns = GridCells.Fixed(spanCount),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
@ -79,7 +155,10 @@ fun EmojiPicker(
is EmojiPickerItem.Category -> {
Text(
stringResource(item.category.nameResource),
fontWeight = FontWeight.Black
style = MaterialTheme.typography.labelMedium,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 8.dp)
)
}
}

View File

@ -76,6 +76,28 @@ class EmojiMetadata {
return list
}
/**
* Returns a map of category to start and end index of the category in the flat picker list.
*/
fun categorySpans(flatPickerList: List<EmojiPickerItem>): Map<EmojiCategory, Pair<Int, Int>> {
val map = mutableMapOf<EmojiCategory, Pair<Int, Int>>()
var start = 0
var end = 0
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
}
end++
}
return map
}
init {
metadata = initMetadata(RevoltApplication.instance.applicationContext)
}

View File

@ -598,6 +598,7 @@ fun ChannelScreen(
.fillMaxWidth()
.fillMaxHeight(0.5f)
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
.padding(4.dp)
) {
EmojiPicker(onEmojiSelected = viewModel::putAtCursorPosition)
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M9.5 3C7.56 3 5.85 4.24 5.23 6.08C3.36 6.44 2 8.09 2 10C2 12.21 3.79 14 6 14V22H17V20H20C20.55 20 21 19.55 21 19V11C21 10.45 20.55 10 20 10H18V8C18 5.79 16.21 4 14 4H12.32C11.5 3.35 10.53 3 9.5 3M9.5 5C10.29 5 11.03 5.37 11.5 6H14C15.11 6 16 6.9 16 8H12C10 8 9.32 9.13 8.5 10.63C7.68 12.13 6 12 6 12C4.89 12 4 11.11 4 10C4 8.9 4.89 8 6 8H7V7.5C7 6.12 8.12 5 9.5 5M17 12H19V18H17Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M1.5,4V5.5C1.5,9.65 3.71,13.28 7,15.3V20H22V18C22,15.34 16.67,14 14,14C14,14 13.83,14 13.75,14C9,14 5,10 5,5.5V4M14,4A4,4 0 0,0 10,8A4,4 0 0,0 14,12A4,4 0 0,0 18,8A4,4 0 0,0 14,4Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M20.95 17C20.7 18.69 19.26 20 17.5 20H16V18H19C18.93 16.72 19.26 14.04 18.53 12.95C17.56 10.9 14.83 10.56 12.93 10.05C12 10 11 9 10.84 8H8C7.72 8 7.5 7.78 7.5 7.5C7.5 7.22 7.72 7 8 7H10.5V6H8C7.72 6 7.5 5.78 7.5 5.5C7.5 5.22 7.72 5 8 5H10.5V2H2.03V18H5V20H1V22H17.5C20.36 22 22.72 19.8 23 17H20.95M14 20H7V18H14V20Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M19.5 17H18C19.1 17 20 16.1 20 15S19.1 13 18 13V9C18 8 18 7 16.92 6.14C16.97 5.93 17 5.72 17 5.5C17 3.57 15 2 12.5 2C10.24 2 8.38 3.31 8.07 5H6L3.71 2.79L3 3.5L5 5.5L3 7.5L3.71 8.21L6 6H8.07C8.38 7.69 10.24 9 12.5 9C13 9 13.5 8.92 13.93 8.8C13.97 8.87 14 8.94 14 9V13H8C6.9 13 6 13.9 6 15S6.9 17 8 17H6.5C5.12 17 4 18.12 4 19.5C4 19.67 4 19.84 4.05 20H4C2.9 20 2 20.9 2 22H19.5C20.88 22 22 20.88 22 19.5S20.88 17 19.5 17M12 5C11.45 5 11 4.55 11 4S11.45 3 12 3 13 3.45 13 4 12.55 5 12 5Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M2 7V14H4V7H2M6 7V9H10V11H8V14H10V13C11.11 13 12 12.11 12 11V9C12 7.89 11.11 7 10 7H6M15.8 7L15.6 9H14V11H15.4L15.2 13H14V15H15L14.8 17H16.8L17 15H18.4L18.2 17H20.2L20.4 15H22V13H20.6L20.8 11H22V9H21L21.2 7H19.2L19 9H17.6L17.8 7H15.8M17.4 11H18.8L18.6 13H17.2L17.4 11M2 15V17H4V15H2M8 15V17H10V15H8Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M12 22H6A2 2 0 0 1 8 20V8H2V5H16V8H10V20A2 2 0 0 1 12 22M22 2V22H20V15H15V22H13V14A2 2 0 0 1 15 12H20V2Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#ffffff"
android:pathData="M5 2H12C12.8 2 13.56 2.32 14.12 2.88C14.68 3.44 15 4.2 15 5V6H13V4H4V11H9V16H8L5 19H4V17L5 16C4.2 16 3.44 15.68 2.88 15.12C2.32 14.56 2 13.8 2 13V5C2 4.2 2.32 3.44 2.88 2.88C3.44 2.32 4.2 2 5 2M5.71 12.29C5.5 12.11 5.27 12 5 12C4.74 12 4.5 12.11 4.29 12.29C4.11 12.5 4 12.74 4 13C4 13.27 4.11 13.5 4.29 13.71C4.5 13.9 4.74 14 5 14C5.27 14 5.5 13.9 5.71 13.71C5.9 13.5 6 13.27 6 13C6 12.74 5.9 12.5 5.71 12.29M11 11C11 9.34 12 8 15 8H18C21 8 22 9.34 22 11V18C22 18.74 21.6 19.39 21 19.73V21C21 21.55 20.55 22 20 22C19.45 22 19 21.55 19 21V20H14V21C14 21.55 13.55 22 13 22C12.45 22 12 21.55 12 21V19.73C11.4 19.39 11 18.74 11 18V11M13 10V14H20V10H13M14 18C14.55 18 15 17.55 15 17C15 16.45 14.55 16 14 16C13.45 16 13 16.45 13 17C13 17.55 13.45 18 14 18M20 17C20 16.45 19.55 16 19 16C18.45 16 18 16.45 18 17C18 17.55 18.45 18 19 18C19.55 18 20 17.55 20 17Z" />
</vector>