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
|
||||
}
|
||||
|
||||
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.Reply
|
||||
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.util.ImmutableList
|
||||
import dev.meloda.fast.ui.util.emptyImmutableList
|
||||
@@ -81,22 +82,6 @@ fun MessageBubble(
|
||||
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) {
|
||||
derivedStateOf { text != null }
|
||||
}
|
||||
@@ -119,6 +104,22 @@ fun MessageBubble(
|
||||
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) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
@@ -217,7 +218,12 @@ fun MessageBubble(
|
||||
topEnd = 0.dp
|
||||
)
|
||||
)
|
||||
.background(backgroundColor)
|
||||
.background(
|
||||
backgroundColor.copy(
|
||||
alpha = if (attachments.firstOrNull() is VkStickerDomain) 0f
|
||||
else 1f
|
||||
)
|
||||
)
|
||||
) {
|
||||
Attachments(
|
||||
modifier = Modifier,
|
||||
|
||||
+31
-4
@@ -1,20 +1,29 @@
|
||||
package dev.meloda.fast.messageshistory.presentation.attachments
|
||||
|
||||
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.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
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.domain.VkAttachment
|
||||
import dev.meloda.fast.model.api.domain.VkAudioDomain
|
||||
import dev.meloda.fast.model.api.domain.VkFileDomain
|
||||
import dev.meloda.fast.model.api.domain.VkLinkDomain
|
||||
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.ui.util.ImmutableList
|
||||
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
|
||||
@@ -31,12 +40,12 @@ fun Attachments(
|
||||
onClick: (VkAttachment) -> Unit = {},
|
||||
onLongClick: (VkAttachment) -> Unit = {}
|
||||
) {
|
||||
if (attachments.isEmpty()) return
|
||||
|
||||
val currentOnClick by rememberUpdatedState(onClick)
|
||||
val currentOnLongClick by rememberUpdatedState(onLongClick)
|
||||
|
||||
Column(modifier = modifier) {
|
||||
if (attachments.isEmpty()) return
|
||||
|
||||
val previewAttachments by remember(attachments) {
|
||||
derivedStateOf {
|
||||
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