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 LinkInfo(val url: 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 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.schemas.AutumnResource
|
||||
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.UserAvatarWidthPlaceholder
|
||||
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) {
|
||||
if (emoji.isUlid()) {
|
||||
RemoteImage(
|
||||
url = "$REVOLT_FILES/emojis/${emoji}/emoji.gif?max_side=64",
|
||||
url = "$REVOLT_FILES/emojis/${emoji}/emoji.gif",
|
||||
description = null,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -269,6 +269,16 @@ class EmojiImpl {
|
|||
}.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 {
|
||||
metadata = initMetadata(RevoltApplication.instance.applicationContext)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ import chat.revolt.sheets.AddServerSheet
|
|||
import chat.revolt.sheets.ChangelogSheet
|
||||
import chat.revolt.sheets.EmoteInfoSheet
|
||||
import chat.revolt.sheets.LinkInfoSheet
|
||||
import chat.revolt.sheets.ReactionInfoSheet
|
||||
import chat.revolt.sheets.ServerContextSheet
|
||||
import chat.revolt.sheets.StatusSheet
|
||||
import chat.revolt.sheets.UserInfoSheet
|
||||
|
|
@ -327,6 +328,10 @@ fun ChatRouterScreen(
|
|||
var showEmoteInfoSheet by remember { mutableStateOf(false) }
|
||||
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) }
|
||||
|
||||
val toggleDrawerLambda = remember {
|
||||
|
|
@ -441,6 +446,12 @@ fun ChatRouterScreen(
|
|||
showEmoteInfoSheet = true
|
||||
}
|
||||
|
||||
is Action.MessageReactionInfo -> {
|
||||
reactionInfoSheetMessageId = action.messageId
|
||||
reactionInfoSheetEmoji = action.emoji
|
||||
showReactionInfoSheet = true
|
||||
}
|
||||
|
||||
is Action.TopNavigate -> {
|
||||
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(
|
||||
modifier = Modifier
|
||||
.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_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_empty">There hasn\'t been a description set for this channel yet.</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue