extract reply bubble view to the components module
This commit is contained in:
parent
d17ee34d78
commit
e024860a77
|
@ -32,7 +32,7 @@ data class BubbleMeta(
|
||||||
fun BubbleMeta.isNotSelf() = !this.isSelf
|
fun BubbleMeta.isNotSelf() = !this.isSelf
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LazyItemScope.AlignedContainer(
|
fun LazyItemScope.AlignedDraggableContainer(
|
||||||
avatar: Avatar,
|
avatar: Avatar,
|
||||||
isSelf: Boolean,
|
isSelf: Boolean,
|
||||||
wasPreviousMessageSameSender: Boolean,
|
wasPreviousMessageSameSender: Boolean,
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
@ -20,72 +21,156 @@ import androidx.compose.ui.unit.sp
|
||||||
import coil.compose.rememberAsyncImagePainter
|
import coil.compose.rememberAsyncImagePainter
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
|
|
||||||
data class Event(val authorName: String, val edited: Boolean, val time: String)
|
sealed interface BubbleModel {
|
||||||
data class ImageContent(val width: Int?, val height: Int?, val url: String)
|
val event: Event
|
||||||
|
|
||||||
|
data class Text(val content: String, override val event: Event) : BubbleModel
|
||||||
|
data class Encrypted(override val event: Event) : BubbleModel
|
||||||
|
data class Image(val imageContent: ImageContent, val imageRequest: ImageRequest, override val event: Event) : BubbleModel {
|
||||||
|
data class ImageContent(val width: Int?, val height: Int?, val url: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Reply(val replyingTo: BubbleModel, val reply: BubbleModel) : BubbleModel {
|
||||||
|
override val event = reply.event
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Event(val authorId: String, val authorName: String, val edited: Boolean, val time: String)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun BubbleModel.Reply.isReplyingToSelf() = this.replyingTo.event.authorId == this.reply.event.authorId
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Bubble(bubble: BubbleMeta, content: @Composable () -> Unit) {
|
fun MessageBubble(bubble: BubbleMeta, model: BubbleModel, status: @Composable () -> Unit) {
|
||||||
Box(modifier = Modifier.padding(start = 6.dp)) {
|
when (model) {
|
||||||
Box(
|
is BubbleModel.Text -> TextBubble(bubble, model, status)
|
||||||
Modifier
|
is BubbleModel.Encrypted -> EncryptedBubble(bubble, model, status)
|
||||||
.padding(4.dp)
|
is BubbleModel.Image -> ImageBubble(bubble, model, status)
|
||||||
.clip(bubble.shape)
|
is BubbleModel.Reply -> ReplyBubble(bubble, model, status)
|
||||||
.background(bubble.background)
|
|
||||||
.height(IntrinsicSize.Max),
|
|
||||||
) {
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TextBubble(bubble: BubbleMeta, event: Event, textContent: String, status: @Composable () -> Unit) {
|
private fun TextBubble(bubble: BubbleMeta, model: BubbleModel.Text, status: @Composable () -> Unit) {
|
||||||
Bubble(bubble) {
|
Bubble(bubble) {
|
||||||
Column(
|
if (bubble.isNotSelf()) {
|
||||||
Modifier
|
AuthorName(model.event, bubble)
|
||||||
.padding(8.dp)
|
|
||||||
.width(IntrinsicSize.Max)
|
|
||||||
.defaultMinSize(minWidth = 50.dp)
|
|
||||||
) {
|
|
||||||
if (bubble.isNotSelf()) {
|
|
||||||
AuthorName(event, bubble)
|
|
||||||
}
|
|
||||||
TextContent(bubble, text = textContent)
|
|
||||||
Footer(event, bubble, status)
|
|
||||||
}
|
}
|
||||||
|
TextContent(bubble, text = model.content)
|
||||||
|
Footer(model.event, bubble, status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EncryptedBubble(bubble: BubbleMeta, event: Event, status: @Composable () -> Unit) {
|
private fun EncryptedBubble(bubble: BubbleMeta, model: BubbleModel.Encrypted, status: @Composable () -> Unit) {
|
||||||
TextBubble(bubble, event, textContent = "Encrypted message", status)
|
TextBubble(bubble, BubbleModel.Text(content = "Encrypted message", model.event), status)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ImageBubble(bubble: BubbleMeta, event: Event, imageContent: ImageContent, status: @Composable () -> Unit, imageRequest: ImageRequest) {
|
private fun ImageBubble(bubble: BubbleMeta, model: BubbleModel.Image, status: @Composable () -> Unit) {
|
||||||
|
Bubble(bubble) {
|
||||||
|
if (bubble.isNotSelf()) {
|
||||||
|
AuthorName(model.event, bubble)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Image(
|
||||||
|
modifier = Modifier.size(model.imageContent.scale(LocalDensity.current, LocalConfiguration.current)),
|
||||||
|
painter = rememberAsyncImagePainter(model = model.imageRequest),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Footer(model.event, bubble, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ReplyBubble(bubble: BubbleMeta, model: BubbleModel.Reply, status: @Composable () -> Unit) {
|
||||||
Bubble(bubble) {
|
Bubble(bubble) {
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
if (bubble.isNotSelf()) SmallTalkTheme.extendedColors.onOthersBubble.copy(alpha = 0.1f) else SmallTalkTheme.extendedColors.onSelfBubble.copy(
|
||||||
|
alpha = 0.2f
|
||||||
|
), RoundedCornerShape(12.dp)
|
||||||
|
)
|
||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
.width(IntrinsicSize.Max)
|
|
||||||
.defaultMinSize(minWidth = 50.dp)
|
|
||||||
) {
|
) {
|
||||||
if (bubble.isNotSelf()) {
|
val replyName = if (!bubble.isNotSelf() && model.isReplyingToSelf()) "You" else model.replyingTo.event.authorName
|
||||||
AuthorName(event, bubble)
|
Text(
|
||||||
}
|
fontSize = 11.sp,
|
||||||
|
text = replyName,
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
maxLines = 1,
|
||||||
Image(
|
color = bubble.textColor()
|
||||||
modifier = Modifier.size(imageContent.scale(LocalDensity.current, LocalConfiguration.current)),
|
|
||||||
painter = rememberAsyncImagePainter(model = imageRequest),
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
)
|
||||||
Footer(event, bubble, status)
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
|
||||||
|
when (val replyingTo = model.replyingTo) {
|
||||||
|
is BubbleModel.Text -> {
|
||||||
|
Text(
|
||||||
|
text = replyingTo.content,
|
||||||
|
color = bubble.textColor().copy(alpha = 0.8f),
|
||||||
|
fontSize = 14.sp,
|
||||||
|
modifier = Modifier.wrapContentSize(),
|
||||||
|
textAlign = TextAlign.Start,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BubbleModel.Encrypted -> {
|
||||||
|
Text(
|
||||||
|
text = "Encrypted message",
|
||||||
|
color = bubble.textColor().copy(alpha = 0.8f),
|
||||||
|
fontSize = 14.sp,
|
||||||
|
modifier = Modifier.wrapContentSize(),
|
||||||
|
textAlign = TextAlign.Start,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BubbleModel.Image -> {
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Image(
|
||||||
|
modifier = Modifier.size(replyingTo.imageContent.scale(LocalDensity.current, LocalConfiguration.current)),
|
||||||
|
painter = rememberAsyncImagePainter(replyingTo.imageRequest),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
is BubbleModel.Reply -> {
|
||||||
|
// TODO - a reply to a reply
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
if (bubble.isNotSelf()) {
|
||||||
|
AuthorName(model.event, bubble)
|
||||||
|
}
|
||||||
|
|
||||||
|
when (val message = model.reply) {
|
||||||
|
is BubbleModel.Text -> TextContent(bubble, message.content)
|
||||||
|
is BubbleModel.Encrypted -> TextContent(bubble, "Encrypted message")
|
||||||
|
is BubbleModel.Image -> {
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Image(
|
||||||
|
modifier = Modifier.size(message.imageContent.scale(LocalDensity.current, LocalConfiguration.current)),
|
||||||
|
painter = rememberAsyncImagePainter(model = message.imageRequest),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BubbleModel.Reply -> {
|
||||||
|
// TODO - a reply to a reply
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Footer(model.event, bubble, status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ImageContent.scale(density: Density, configuration: Configuration): DpSize {
|
private fun BubbleModel.Image.ImageContent.scale(density: Density, configuration: Configuration): DpSize {
|
||||||
val height = this@scale.height ?: 250
|
val height = this@scale.height ?: 250
|
||||||
val width = this@scale.width ?: 250
|
val width = this@scale.width ?: 250
|
||||||
return with(density) {
|
return with(density) {
|
||||||
|
@ -101,14 +186,35 @@ private fun ImageContent.scale(density: Density, configuration: Configuration):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun Int.scalerFor(max: Float): Float {
|
private fun Int.scalerFor(max: Float): Float {
|
||||||
return max / this
|
return max / this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Footer(event: Event, bubble: BubbleMeta, status: @Composable () -> Unit) {
|
private fun Bubble(bubble: BubbleMeta, content: @Composable () -> Unit) {
|
||||||
|
Box(modifier = Modifier.padding(start = 6.dp)) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.padding(4.dp)
|
||||||
|
.clip(bubble.shape)
|
||||||
|
.background(bubble.background)
|
||||||
|
.height(IntrinsicSize.Max),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.width(IntrinsicSize.Max)
|
||||||
|
.defaultMinSize(minWidth = 50.dp)
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Footer(event: BubbleModel.Event, bubble: BubbleMeta, status: @Composable () -> Unit) {
|
||||||
Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 2.dp)) {
|
Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 2.dp)) {
|
||||||
val editedPrefix = if (event.edited) "(edited) " else null
|
val editedPrefix = if (event.edited) "(edited) " else null
|
||||||
Text(
|
Text(
|
||||||
|
@ -134,7 +240,7 @@ private fun TextContent(bubble: BubbleMeta, text: String) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AuthorName(event: Event, bubble: BubbleMeta) {
|
private fun AuthorName(event: BubbleModel.Event, bubble: BubbleMeta) {
|
||||||
Text(
|
Text(
|
||||||
fontSize = 11.sp,
|
fontSize = 11.sp,
|
||||||
text = event.authorName,
|
text = event.authorName,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package app.dapk.st.messenger
|
package app.dapk.st.messenger
|
||||||
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
@ -24,10 +23,8 @@ import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
@ -189,208 +186,40 @@ private fun ColumnScope.RoomContent(self: UserId, state: RoomState, replyActions
|
||||||
val previousEvent = if (index != 0) state.events[index - 1] else null
|
val previousEvent = if (index != 0) state.events[index - 1] else null
|
||||||
val wasPreviousMessageSameSender = previousEvent?.author?.id == item.author.id
|
val wasPreviousMessageSameSender = previousEvent?.author?.id == item.author.id
|
||||||
|
|
||||||
AlignedContainer(
|
AlignedDraggableContainer(
|
||||||
avatar = Avatar(item.author.avatarUrl?.value, item.author.displayName ?: item.author.id.value),
|
avatar = Avatar(item.author.avatarUrl?.value, item.author.displayName ?: item.author.id.value),
|
||||||
isSelf = self == item.author.id,
|
isSelf = self == item.author.id,
|
||||||
wasPreviousMessageSameSender = wasPreviousMessageSameSender,
|
wasPreviousMessageSameSender = wasPreviousMessageSameSender,
|
||||||
onReply = { replyActions.onReply(item) },
|
onReply = { replyActions.onReply(item) },
|
||||||
) {
|
) {
|
||||||
val event = Event(item.author.displayName ?: item.author.id.value, item.edited, item.time)
|
val event = BubbleModel.Event(item.author.id.value, item.author.displayName ?: item.author.id.value, item.edited, item.time)
|
||||||
val status = @Composable { SendStatus(item) }
|
val status = @Composable { SendStatus(item) }
|
||||||
when (item) {
|
MessageBubble(this, item.toModel(event), status)
|
||||||
is RoomEvent.Image -> {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val imageRequest = ImageRequest.Builder(context)
|
|
||||||
.fetcherFactory(LocalDecyptingFetcherFactory.current)
|
|
||||||
.memoryCacheKey(item.imageMeta.url)
|
|
||||||
.data(item)
|
|
||||||
.build()
|
|
||||||
ImageBubble(this, event, ImageContent(item.imageMeta.width, item.imageMeta.height, item.imageMeta.url), status, imageRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
is RoomEvent.Message -> TextBubble(this, event, item.content, status = status)
|
|
||||||
is RoomEvent.Reply -> ReplyBubble(this, item)
|
|
||||||
is RoomEvent.Encrypted -> EncryptedBubble(this, event, status)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RoomEvent.Image.ImageMeta.scale(density: Density, configuration: Configuration): DpSize {
|
@Composable
|
||||||
val height = this@scale.height ?: 250
|
private fun RoomEvent.toModel(event: BubbleModel.Event): BubbleModel = when (this) {
|
||||||
val width = this@scale.width ?: 250
|
is RoomEvent.Message -> BubbleModel.Text(this.content, event)
|
||||||
return with(density) {
|
is RoomEvent.Encrypted -> BubbleModel.Encrypted(event)
|
||||||
val scaler = minOf(
|
is RoomEvent.Image -> {
|
||||||
height.scalerFor(configuration.screenHeightDp.dp.toPx() * 0.5f),
|
val context = LocalContext.current
|
||||||
width.scalerFor(configuration.screenWidthDp.dp.toPx() * 0.6f)
|
val imageRequest = ImageRequest.Builder(context)
|
||||||
)
|
.fetcherFactory(LocalDecyptingFetcherFactory.current)
|
||||||
|
.memoryCacheKey(this.imageMeta.url)
|
||||||
|
.data(this)
|
||||||
|
.build()
|
||||||
|
val imageContent = BubbleModel.Image.ImageContent(this.imageMeta.width, this.imageMeta.height, this.imageMeta.url)
|
||||||
|
BubbleModel.Image(imageContent, imageRequest, event)
|
||||||
|
}
|
||||||
|
|
||||||
DpSize(
|
is RoomEvent.Reply -> {
|
||||||
width = (width * scaler).toDp(),
|
BubbleModel.Reply(this.replyingTo.toModel(event), this.message.toModel(event))
|
||||||
height = (height * scaler).toDp(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun Int.scalerFor(max: Float): Float {
|
|
||||||
return max / this
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun BubbleMeta.textColor(): Color {
|
|
||||||
return if (this.isSelf) SmallTalkTheme.extendedColors.onSelfBubble else SmallTalkTheme.extendedColors.onOthersBubble
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ReplyBubble(bubble: BubbleMeta, event: RoomEvent.Reply) {
|
|
||||||
Box(modifier = Modifier.padding(start = 6.dp)) {
|
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.padding(4.dp)
|
|
||||||
.clip(bubble.shape)
|
|
||||||
.background(bubble.background)
|
|
||||||
.height(IntrinsicSize.Max),
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.width(IntrinsicSize.Max)
|
|
||||||
.defaultMinSize(minWidth = 50.dp)
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
Column(
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(
|
|
||||||
if (bubble.isNotSelf()) SmallTalkTheme.extendedColors.onOthersBubble.copy(alpha = 0.1f) else SmallTalkTheme.extendedColors.onSelfBubble.copy(
|
|
||||||
alpha = 0.2f
|
|
||||||
), RoundedCornerShape(12.dp)
|
|
||||||
)
|
|
||||||
.padding(8.dp)
|
|
||||||
) {
|
|
||||||
val replyName = if (!bubble.isNotSelf() && event.replyingToSelf) "You" else event.replyingTo.author.displayName
|
|
||||||
?: event.replyingTo.author.id.value
|
|
||||||
Text(
|
|
||||||
fontSize = 11.sp,
|
|
||||||
text = replyName,
|
|
||||||
maxLines = 1,
|
|
||||||
color = bubble.textColor()
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
|
||||||
when (val replyingTo = event.replyingTo) {
|
|
||||||
is RoomEvent.Message -> {
|
|
||||||
Text(
|
|
||||||
text = replyingTo.content,
|
|
||||||
color = bubble.textColor().copy(alpha = 0.8f),
|
|
||||||
fontSize = 14.sp,
|
|
||||||
modifier = Modifier.wrapContentSize(),
|
|
||||||
textAlign = TextAlign.Start,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is RoomEvent.Image -> {
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
Image(
|
|
||||||
modifier = Modifier.size(replyingTo.imageMeta.scale(LocalDensity.current, LocalConfiguration.current)),
|
|
||||||
painter = rememberAsyncImagePainter(
|
|
||||||
model = ImageRequest.Builder(context)
|
|
||||||
.fetcherFactory(LocalDecyptingFetcherFactory.current)
|
|
||||||
.memoryCacheKey(replyingTo.imageMeta.url)
|
|
||||||
.data(replyingTo)
|
|
||||||
.build()
|
|
||||||
),
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
}
|
|
||||||
|
|
||||||
is RoomEvent.Reply -> {
|
|
||||||
// TODO - a reply to a reply
|
|
||||||
}
|
|
||||||
|
|
||||||
is RoomEvent.Encrypted -> {
|
|
||||||
Text(
|
|
||||||
text = "Encrypted message",
|
|
||||||
color = bubble.textColor().copy(alpha = 0.8f),
|
|
||||||
fontSize = 14.sp,
|
|
||||||
modifier = Modifier.wrapContentSize(),
|
|
||||||
textAlign = TextAlign.Start,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
|
|
||||||
if (bubble.isNotSelf()) {
|
|
||||||
Text(
|
|
||||||
fontSize = 11.sp,
|
|
||||||
text = event.message.author.displayName ?: event.message.author.id.value,
|
|
||||||
maxLines = 1,
|
|
||||||
color = bubble.textColor()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
when (val message = event.message) {
|
|
||||||
is RoomEvent.Message -> {
|
|
||||||
Text(
|
|
||||||
text = message.content,
|
|
||||||
color = bubble.textColor(),
|
|
||||||
fontSize = 15.sp,
|
|
||||||
modifier = Modifier.wrapContentSize(),
|
|
||||||
textAlign = TextAlign.Start,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
is RoomEvent.Image -> {
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
Image(
|
|
||||||
modifier = Modifier.size(message.imageMeta.scale(LocalDensity.current, LocalConfiguration.current)),
|
|
||||||
painter = rememberAsyncImagePainter(
|
|
||||||
model = ImageRequest.Builder(context)
|
|
||||||
.data(message)
|
|
||||||
.memoryCacheKey(message.imageMeta.url)
|
|
||||||
.fetcherFactory(LocalDecyptingFetcherFactory.current)
|
|
||||||
.build()
|
|
||||||
),
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
}
|
|
||||||
|
|
||||||
is RoomEvent.Reply -> {
|
|
||||||
// TODO - a reply to a reply
|
|
||||||
}
|
|
||||||
|
|
||||||
is RoomEvent.Encrypted -> {
|
|
||||||
Text(
|
|
||||||
text = "Encrypted message",
|
|
||||||
color = bubble.textColor(),
|
|
||||||
fontSize = 15.sp,
|
|
||||||
modifier = Modifier.wrapContentSize(),
|
|
||||||
textAlign = TextAlign.Start,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
|
||||||
Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
|
||||||
Text(
|
|
||||||
fontSize = 9.sp,
|
|
||||||
text = event.time,
|
|
||||||
textAlign = TextAlign.End,
|
|
||||||
color = bubble.textColor(),
|
|
||||||
modifier = Modifier.wrapContentSize()
|
|
||||||
)
|
|
||||||
SendStatus(event.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SendStatus(message: RoomEvent) {
|
private fun SendStatus(message: RoomEvent) {
|
||||||
when (val meta = message.meta) {
|
when (val meta = message.meta) {
|
||||||
|
|
Loading…
Reference in New Issue