forked from melod1n/fast-messenger
feat: Display stickers in messages (#200)
This commit introduces the ability to display stickers within message bubbles.
Key changes:
- `Attachments.kt`: Added handling for `AttachmentType.STICKER`. If an attachment type is unsupported, a placeholder text is now displayed.
- `Sticker.kt`: New composable created to render `VkStickerDomain` using `AsyncImage`.
- `MessageBubble.kt`:
- Adjusted background alpha for sticker messages to make the bubble transparent.
- Minor refactoring of `minDateContainerWidth` and `dateContainerWidth` initialization.
- `VkStickerDomain.kt`: Added `getUrl()` function to construct sticker image URLs, with options for specifying width and background.
This commit is contained in:
@@ -21,4 +21,12 @@ data class VkStickerDomain(
|
|||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getUrl(width: Int = 256, withBackground: Boolean = false): String? = when {
|
||||||
|
withBackground && backgroundImages != null -> {
|
||||||
|
backgroundImages.firstOrNull { it.width >= width }?.url
|
||||||
|
}
|
||||||
|
images != null -> images.firstOrNull { it.width >= width }?.url
|
||||||
|
else -> "https://vk.com/sticker/1-${id}-${width}b"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-17
@@ -35,6 +35,7 @@ import dev.meloda.fast.messageshistory.model.SendingStatus
|
|||||||
import dev.meloda.fast.messageshistory.presentation.attachments.Attachments
|
import dev.meloda.fast.messageshistory.presentation.attachments.Attachments
|
||||||
import dev.meloda.fast.messageshistory.presentation.attachments.Reply
|
import dev.meloda.fast.messageshistory.presentation.attachments.Reply
|
||||||
import dev.meloda.fast.model.api.domain.VkAttachment
|
import dev.meloda.fast.model.api.domain.VkAttachment
|
||||||
|
import dev.meloda.fast.model.api.domain.VkStickerDomain
|
||||||
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
import dev.meloda.fast.ui.theme.LocalThemeConfig
|
||||||
import dev.meloda.fast.ui.util.ImmutableList
|
import dev.meloda.fast.ui.util.ImmutableList
|
||||||
import dev.meloda.fast.ui.util.emptyImmutableList
|
import dev.meloda.fast.ui.util.emptyImmutableList
|
||||||
@@ -81,22 +82,6 @@ fun MessageBubble(
|
|||||||
MaterialTheme.colorScheme.onPrimaryContainer
|
MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
val minDateContainerWidth by remember(isEdited, isOut, isPinned, isImportant) {
|
|
||||||
derivedStateOf {
|
|
||||||
val mainPart = if (isEdited) 50.dp else 30.dp
|
|
||||||
val readIndicatorPart = if (isOut) 14.dp else 0.dp
|
|
||||||
val pinnedIndicatorPart = if (isPinned) 14.dp else 0.dp
|
|
||||||
val importantIndicatorPart = if (isImportant) 14.dp else 0.dp
|
|
||||||
|
|
||||||
mainPart + readIndicatorPart + pinnedIndicatorPart + importantIndicatorPart
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val dateContainerWidth by animateDpAsState(
|
|
||||||
targetValue = minDateContainerWidth,
|
|
||||||
label = "dateContainerWidth"
|
|
||||||
)
|
|
||||||
|
|
||||||
val shouldShowBubble by remember(text) {
|
val shouldShowBubble by remember(text) {
|
||||||
derivedStateOf { text != null }
|
derivedStateOf { text != null }
|
||||||
}
|
}
|
||||||
@@ -119,6 +104,22 @@ fun MessageBubble(
|
|||||||
mutableIntStateOf(0)
|
mutableIntStateOf(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val minDateContainerWidth by remember(isEdited, isOut, isPinned, isImportant) {
|
||||||
|
derivedStateOf {
|
||||||
|
val mainPart = if (isEdited) 50 else 30
|
||||||
|
val readIndicatorPart = if (isOut) 14 else 0
|
||||||
|
val pinnedIndicatorPart = if (isPinned) 14 else 0
|
||||||
|
val importantIndicatorPart = if (isImportant) 14 else 0
|
||||||
|
|
||||||
|
(mainPart + readIndicatorPart + pinnedIndicatorPart + importantIndicatorPart).dp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val dateContainerWidth by animateDpAsState(
|
||||||
|
targetValue = minDateContainerWidth,
|
||||||
|
label = "dateContainerWidth"
|
||||||
|
)
|
||||||
|
|
||||||
CompositionLocalProvider(LocalContentColor provides contentColor) {
|
CompositionLocalProvider(LocalContentColor provides contentColor) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@@ -217,7 +218,12 @@ fun MessageBubble(
|
|||||||
topEnd = 0.dp
|
topEnd = 0.dp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.background(backgroundColor)
|
.background(
|
||||||
|
backgroundColor.copy(
|
||||||
|
alpha = if (attachments.firstOrNull() is VkStickerDomain) 0f
|
||||||
|
else 1f
|
||||||
|
)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Attachments(
|
Attachments(
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
|
|||||||
+31
-4
@@ -1,20 +1,29 @@
|
|||||||
package dev.meloda.fast.messageshistory.presentation.attachments
|
package dev.meloda.fast.messageshistory.presentation.attachments
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import dev.meloda.fast.model.api.data.AttachmentType
|
import dev.meloda.fast.model.api.data.AttachmentType
|
||||||
import dev.meloda.fast.model.api.domain.VkAttachment
|
import dev.meloda.fast.model.api.domain.VkAttachment
|
||||||
import dev.meloda.fast.model.api.domain.VkAudioDomain
|
import dev.meloda.fast.model.api.domain.VkAudioDomain
|
||||||
import dev.meloda.fast.model.api.domain.VkFileDomain
|
import dev.meloda.fast.model.api.domain.VkFileDomain
|
||||||
import dev.meloda.fast.model.api.domain.VkLinkDomain
|
import dev.meloda.fast.model.api.domain.VkLinkDomain
|
||||||
import dev.meloda.fast.model.api.domain.VkPhotoDomain
|
import dev.meloda.fast.model.api.domain.VkPhotoDomain
|
||||||
|
import dev.meloda.fast.model.api.domain.VkStickerDomain
|
||||||
import dev.meloda.fast.model.api.domain.VkVideoDomain
|
import dev.meloda.fast.model.api.domain.VkVideoDomain
|
||||||
import dev.meloda.fast.ui.util.ImmutableList
|
import dev.meloda.fast.ui.util.ImmutableList
|
||||||
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
|
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
|
||||||
@@ -31,12 +40,12 @@ fun Attachments(
|
|||||||
onClick: (VkAttachment) -> Unit = {},
|
onClick: (VkAttachment) -> Unit = {},
|
||||||
onLongClick: (VkAttachment) -> Unit = {}
|
onLongClick: (VkAttachment) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
|
if (attachments.isEmpty()) return
|
||||||
|
|
||||||
val currentOnClick by rememberUpdatedState(onClick)
|
val currentOnClick by rememberUpdatedState(onClick)
|
||||||
val currentOnLongClick by rememberUpdatedState(onLongClick)
|
val currentOnLongClick by rememberUpdatedState(onLongClick)
|
||||||
|
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
if (attachments.isEmpty()) return
|
|
||||||
|
|
||||||
val previewAttachments by remember(attachments) {
|
val previewAttachments by remember(attachments) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
attachments.values.filter { it.type in previewTypes }
|
attachments.values.filter { it.type in previewTypes }
|
||||||
@@ -92,7 +101,25 @@ fun Attachments(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> Unit
|
AttachmentType.STICKER -> {
|
||||||
|
Sticker(
|
||||||
|
item = attachment as VkStickerDomain
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
Text(
|
||||||
|
text = buildAnnotatedString {
|
||||||
|
append("Unsupported attachment: [${attachment.type}]")
|
||||||
|
addStyle(SpanStyle(fontWeight = FontWeight.Medium), 0, length)
|
||||||
|
addStyle(SpanStyle(fontStyle = FontStyle.Italic), 0, length)
|
||||||
|
addStyle(SpanStyle(textDecoration = TextDecoration.Underline), 0, length)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
package dev.meloda.fast.messageshistory.presentation.attachments
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import dev.meloda.fast.model.api.domain.VkStickerDomain
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Sticker(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
item: VkStickerDomain
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier.size(192.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
AsyncImage(
|
||||||
|
model = item.getUrl(
|
||||||
|
width = 256,
|
||||||
|
withBackground = false
|
||||||
|
),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxSize()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user