feat: category list for unicode emoji
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
c4f2d10cb5
commit
6736a2677d
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -598,6 +598,7 @@ fun ChannelScreen(
|
|||
.fillMaxWidth()
|
||||
.fillMaxHeight(0.5f)
|
||||
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp))
|
||||
.padding(4.dp)
|
||||
) {
|
||||
EmojiPicker(onEmojiSelected = viewModel::putAtCursorPosition)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
Loading…
Reference in New Issue