feat: reaction info sheet
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
8b1dc84237
commit
7b011e3206
|
|
@ -7,6 +7,7 @@ sealed class Action {
|
||||||
data class SwitchChannel(val channelId: String) : Action()
|
data class SwitchChannel(val channelId: String) : Action()
|
||||||
data class LinkInfo(val url: String) : Action()
|
data class LinkInfo(val url: String) : Action()
|
||||||
data class EmoteInfo(val emoteId: String) : Action()
|
data class EmoteInfo(val emoteId: String) : Action()
|
||||||
|
data class MessageReactionInfo(val messageId: String, val emoji: String) : Action()
|
||||||
data class TopNavigate(val route: String) : Action()
|
data class TopNavigate(val route: String) : Action()
|
||||||
data class ChatNavigate(val route: String) : Action()
|
data class ChatNavigate(val route: String) : Action()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,8 @@ import chat.revolt.api.routes.channel.unreact
|
||||||
import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl
|
import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl
|
||||||
import chat.revolt.api.schemas.AutumnResource
|
import chat.revolt.api.schemas.AutumnResource
|
||||||
import chat.revolt.api.schemas.User
|
import chat.revolt.api.schemas.User
|
||||||
|
import chat.revolt.callbacks.Action
|
||||||
|
import chat.revolt.callbacks.ActionChannel
|
||||||
import chat.revolt.components.generic.UserAvatar
|
import chat.revolt.components.generic.UserAvatar
|
||||||
import chat.revolt.components.generic.UserAvatarWidthPlaceholder
|
import chat.revolt.components.generic.UserAvatarWidthPlaceholder
|
||||||
import chat.revolt.internals.markdown.LongClickableSpan
|
import chat.revolt.internals.markdown.LongClickableSpan
|
||||||
|
|
@ -488,7 +490,14 @@ fun Message(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
scope.launch {
|
||||||
|
ActionChannel.send(
|
||||||
|
Action.MessageReactionInfo(
|
||||||
|
message.id!!,
|
||||||
|
reaction.key
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ fun Reaction(
|
||||||
CompositionLocalProvider(LocalContentColor provides foreground) {
|
CompositionLocalProvider(LocalContentColor provides foreground) {
|
||||||
if (emoji.isUlid()) {
|
if (emoji.isUlid()) {
|
||||||
RemoteImage(
|
RemoteImage(
|
||||||
url = "$REVOLT_FILES/emojis/${emoji}/emoji.gif?max_side=64",
|
url = "$REVOLT_FILES/emojis/${emoji}/emoji.gif",
|
||||||
description = null,
|
description = null,
|
||||||
modifier = Modifier.size(16.dp)
|
modifier = Modifier.size(16.dp)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -269,6 +269,16 @@ class EmojiImpl {
|
||||||
}.flatten().toList()
|
}.flatten().toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun unicodeAsShortcode(unicode: String): String? {
|
||||||
|
return metadata.asSequence().mapNotNull { group ->
|
||||||
|
group.emoji.find { emoji ->
|
||||||
|
emoji.base.joinToString("") { String(Character.toChars(it.toInt())) } == unicode
|
||||||
|
}
|
||||||
|
}.firstOrNull().let { emoji ->
|
||||||
|
emoji?.shortcodes?.firstOrNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
metadata = initMetadata(RevoltApplication.instance.applicationContext)
|
metadata = initMetadata(RevoltApplication.instance.applicationContext)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,7 @@ import chat.revolt.sheets.AddServerSheet
|
||||||
import chat.revolt.sheets.ChangelogSheet
|
import chat.revolt.sheets.ChangelogSheet
|
||||||
import chat.revolt.sheets.EmoteInfoSheet
|
import chat.revolt.sheets.EmoteInfoSheet
|
||||||
import chat.revolt.sheets.LinkInfoSheet
|
import chat.revolt.sheets.LinkInfoSheet
|
||||||
|
import chat.revolt.sheets.ReactionInfoSheet
|
||||||
import chat.revolt.sheets.ServerContextSheet
|
import chat.revolt.sheets.ServerContextSheet
|
||||||
import chat.revolt.sheets.StatusSheet
|
import chat.revolt.sheets.StatusSheet
|
||||||
import chat.revolt.sheets.UserInfoSheet
|
import chat.revolt.sheets.UserInfoSheet
|
||||||
|
|
@ -327,6 +328,10 @@ fun ChatRouterScreen(
|
||||||
var showEmoteInfoSheet by remember { mutableStateOf(false) }
|
var showEmoteInfoSheet by remember { mutableStateOf(false) }
|
||||||
var emoteInfoSheetTarget by remember { mutableStateOf("") }
|
var emoteInfoSheetTarget by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
var showReactionInfoSheet by remember { mutableStateOf(false) }
|
||||||
|
var reactionInfoSheetMessageId by remember { mutableStateOf("") }
|
||||||
|
var reactionInfoSheetEmoji by remember { mutableStateOf("") }
|
||||||
|
|
||||||
var useTabletAwareUI by remember { mutableStateOf(false) }
|
var useTabletAwareUI by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val toggleDrawerLambda = remember {
|
val toggleDrawerLambda = remember {
|
||||||
|
|
@ -441,6 +446,12 @@ fun ChatRouterScreen(
|
||||||
showEmoteInfoSheet = true
|
showEmoteInfoSheet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is Action.MessageReactionInfo -> {
|
||||||
|
reactionInfoSheetMessageId = action.messageId
|
||||||
|
reactionInfoSheetEmoji = action.emoji
|
||||||
|
showReactionInfoSheet = true
|
||||||
|
}
|
||||||
|
|
||||||
is Action.TopNavigate -> {
|
is Action.TopNavigate -> {
|
||||||
topNav.navigate(action.route)
|
topNav.navigate(action.route)
|
||||||
}
|
}
|
||||||
|
|
@ -676,6 +687,25 @@ fun ChatRouterScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showReactionInfoSheet) {
|
||||||
|
val reactionInfoSheetState = rememberModalBottomSheetState()
|
||||||
|
|
||||||
|
ModalBottomSheet(
|
||||||
|
sheetState = reactionInfoSheetState,
|
||||||
|
onDismissRequest = {
|
||||||
|
showReactionInfoSheet = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
ReactionInfoSheet(
|
||||||
|
messageId = reactionInfoSheetMessageId,
|
||||||
|
emoji = reactionInfoSheetEmoji,
|
||||||
|
onDismiss = {
|
||||||
|
showReactionInfoSheet = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,221 @@
|
||||||
|
package chat.revolt.sheets
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ScrollableTabRow
|
||||||
|
import androidx.compose.material3.Tab
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
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.res.stringResource
|
||||||
|
import androidx.compose.ui.text.PlatformTextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import chat.revolt.R
|
||||||
|
import chat.revolt.api.REVOLT_FILES
|
||||||
|
import chat.revolt.api.RevoltAPI
|
||||||
|
import chat.revolt.api.internals.MessageProcessor
|
||||||
|
import chat.revolt.api.internals.isUlid
|
||||||
|
import chat.revolt.api.routes.custom.fetchEmoji
|
||||||
|
import chat.revolt.api.schemas.Emoji
|
||||||
|
import chat.revolt.api.schemas.User
|
||||||
|
import chat.revolt.components.generic.RemoteImage
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
fun ReactionInfoSheet(messageId: String, emoji: String, onDismiss: () -> Unit) {
|
||||||
|
val message = RevoltAPI.messageCache[messageId] ?: return
|
||||||
|
val channel = RevoltAPI.channelCache[message.channel] ?: return
|
||||||
|
val reactions = message.reactions
|
||||||
|
val reactionEmoji = reactions?.keys?.toList()
|
||||||
|
|
||||||
|
val extendedEmojiInfo = remember(emoji) { mutableStateListOf<Emoji>() }
|
||||||
|
|
||||||
|
LaunchedEffect(reactionEmoji) {
|
||||||
|
reactionEmoji?.forEach {
|
||||||
|
if (it.isUlid()) {
|
||||||
|
extendedEmojiInfo.add(RevoltAPI.emojiCache[it] ?: fetchEmoji(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedReactionIndex by remember(
|
||||||
|
messageId,
|
||||||
|
emoji
|
||||||
|
) { mutableIntStateOf(reactionEmoji?.indexOfFirst { it == emoji } ?: 0) }
|
||||||
|
|
||||||
|
if (selectedReactionIndex >= (reactionEmoji?.size ?: 0)) {
|
||||||
|
selectedReactionIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reactionEmoji?.isEmpty() == true) {
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn {
|
||||||
|
stickyHeader(key = "tabs") {
|
||||||
|
ScrollableTabRow(
|
||||||
|
selectedTabIndex = selectedReactionIndex,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
divider = {}
|
||||||
|
) {
|
||||||
|
reactionEmoji?.forEachIndexed { index, emoji ->
|
||||||
|
Tab(
|
||||||
|
text = {
|
||||||
|
if (emoji.isUlid()) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
RemoteImage(
|
||||||
|
url = "$REVOLT_FILES/emojis/${emoji}/emoji.gif",
|
||||||
|
description = null,
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
|
)
|
||||||
|
Spacer(Modifier.size(4.dp))
|
||||||
|
Text("${reactions[emoji]?.size ?: 0}")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Text("$emoji ${reactions[emoji]?.size ?: 0}")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selected = selectedReactionIndex == index,
|
||||||
|
onClick = { selectedReactionIndex = index }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reactionEmoji?.isNotEmpty() == true) {
|
||||||
|
item("info") {
|
||||||
|
val current = reactionEmoji[selectedReactionIndex]
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(0.dp))
|
||||||
|
) {
|
||||||
|
if (current.isUlid()) {
|
||||||
|
val cached = extendedEmojiInfo.find { it.id == current }
|
||||||
|
RemoteImage(
|
||||||
|
url = "$REVOLT_FILES/emojis/$current/emoji.gif",
|
||||||
|
description = cached?.name,
|
||||||
|
contentScale = ContentScale.Fit,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.size(32.dp)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.size(32.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = current,
|
||||||
|
style = MaterialTheme.typography.bodyLarge.copy(
|
||||||
|
fontSize = 28.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
platformStyle = PlatformTextStyle(
|
||||||
|
includeFontPadding = false
|
||||||
|
)
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(64.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
top = 16.dp,
|
||||||
|
start = 0.dp,
|
||||||
|
end = 16.dp,
|
||||||
|
bottom = 16.dp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (current.isUlid()) {
|
||||||
|
val cached = extendedEmojiInfo.find { it.id == current }
|
||||||
|
Text(
|
||||||
|
text = ":${cached?.name ?: current}:",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
letterSpacing = 1.15.sp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = MessageProcessor.emoji.unicodeAsShortcode(current)
|
||||||
|
?: current,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
letterSpacing = 1.15.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = if (current.isUlid()) {
|
||||||
|
val cached = extendedEmojiInfo.find { it.id == current }
|
||||||
|
if (cached?.parent != null) {
|
||||||
|
when (cached.parent.type) {
|
||||||
|
"Server" -> RevoltAPI.serverCache[cached.parent.id]?.name?.let {
|
||||||
|
stringResource(
|
||||||
|
id = R.string.emote_info_from_server,
|
||||||
|
it
|
||||||
|
)
|
||||||
|
}
|
||||||
|
?: stringResource(id = R.string.emote_info_from_server_unknown)
|
||||||
|
|
||||||
|
else -> stringResource(id = R.string.emote_info_from_server_unknown)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stringResource(id = R.string.emote_info_from_server_unknown)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stringResource(id = R.string.emote_info_from_unicode)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val reactionsForEmoji = reactions[reactionEmoji[selectedReactionIndex]]
|
||||||
|
items(reactionsForEmoji?.size ?: 0) { index ->
|
||||||
|
val reaction = reactionsForEmoji?.get(index) ?: return@items
|
||||||
|
val user = RevoltAPI.userCache[reaction] ?: User.getPlaceholder(reaction)
|
||||||
|
val member = if (channel.server != null && user.id != null) {
|
||||||
|
RevoltAPI.members.getMember(channel.server, user.id)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
MemberListMemberUser(user = user, member = member, serverId = channel.server) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item("bottom") {
|
||||||
|
Spacer(Modifier.size(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -229,6 +229,7 @@
|
||||||
|
|
||||||
<string name="emote_info_from_server">from %1$s</string>
|
<string name="emote_info_from_server">from %1$s</string>
|
||||||
<string name="emote_info_from_server_unknown">from a private server</string>
|
<string name="emote_info_from_server_unknown">from a private server</string>
|
||||||
|
<string name="emote_info_from_unicode">A default emoji that you can use anywhere</string>
|
||||||
|
|
||||||
<string name="channel_info_sheet_description">Channel description</string>
|
<string name="channel_info_sheet_description">Channel description</string>
|
||||||
<string name="channel_info_sheet_description_empty">There hasn\'t been a description set for this channel yet.</string>
|
<string name="channel_info_sheet_description_empty">There hasn\'t been a description set for this channel yet.</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue