From d17ee34d78878a12443d662c79707fec61ac01db Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 18 Oct 2022 22:25:52 +0100 Subject: [PATCH] porting image bubble to design system --- .../app/dapk/st/design/components/Bubble.kt | 127 ++++++++++++++---- .../app/dapk/st/messenger/MessengerScreen.kt | 118 ++-------------- 2 files changed, 111 insertions(+), 134 deletions(-) diff --git a/design-library/src/main/kotlin/app/dapk/st/design/components/Bubble.kt b/design-library/src/main/kotlin/app/dapk/st/design/components/Bubble.kt index 667cbbc..0a61c84 100644 --- a/design-library/src/main/kotlin/app/dapk/st/design/components/Bubble.kt +++ b/design-library/src/main/kotlin/app/dapk/st/design/components/Bubble.kt @@ -1,5 +1,7 @@ package app.dapk.st.design.components +import android.content.res.Configuration +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.material3.Text @@ -8,11 +10,18 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import coil.compose.rememberAsyncImagePainter +import coil.request.ImageRequest data class Event(val authorName: String, val edited: Boolean, val time: String) +data class ImageContent(val width: Int?, val height: Int?, val url: String) @Composable fun Bubble(bubble: BubbleMeta, content: @Composable () -> Unit) { @@ -30,7 +39,7 @@ fun Bubble(bubble: BubbleMeta, content: @Composable () -> Unit) { } @Composable -fun TextBubbleContent(bubble: BubbleMeta, event: Event, textContent: String, status: @Composable () -> Unit) { +fun TextBubble(bubble: BubbleMeta, event: Event, textContent: String, status: @Composable () -> Unit) { Bubble(bubble) { Column( Modifier @@ -39,37 +48,101 @@ fun TextBubbleContent(bubble: BubbleMeta, event: Event, textContent: String, sta .defaultMinSize(minWidth = 50.dp) ) { if (bubble.isNotSelf()) { - Text( - fontSize = 11.sp, - text = event.authorName, - maxLines = 1, - color = bubble.textColor() - ) - } - Text( - text = textContent, - 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()) { - val editedPrefix = if (event.edited) "(edited) " else null - Text( - fontSize = 9.sp, - text = "${editedPrefix ?: ""}${event.time}", - textAlign = TextAlign.End, - color = bubble.textColor(), - modifier = Modifier.wrapContentSize() - ) - status() + AuthorName(event, bubble) } + TextContent(bubble, text = textContent) + Footer(event, bubble, status) } } } +@Composable +fun EncryptedBubble(bubble: BubbleMeta, event: Event, status: @Composable () -> Unit) { + TextBubble(bubble, event, textContent = "Encrypted message", status) +} + +@Composable +fun ImageBubble(bubble: BubbleMeta, event: Event, imageContent: ImageContent, status: @Composable () -> Unit, imageRequest: ImageRequest) { + Bubble(bubble) { + Column( + Modifier + .padding(8.dp) + .width(IntrinsicSize.Max) + .defaultMinSize(minWidth = 50.dp) + ) { + if (bubble.isNotSelf()) { + AuthorName(event, bubble) + } + + Spacer(modifier = Modifier.height(4.dp)) + Image( + modifier = Modifier.size(imageContent.scale(LocalDensity.current, LocalConfiguration.current)), + painter = rememberAsyncImagePainter(model = imageRequest), + contentDescription = null, + ) + Footer(event, bubble, status) + } + } +} + +private fun ImageContent.scale(density: Density, configuration: Configuration): DpSize { + val height = this@scale.height ?: 250 + val width = this@scale.width ?: 250 + return with(density) { + val scaler = minOf( + height.scalerFor(configuration.screenHeightDp.dp.toPx() * 0.5f), + width.scalerFor(configuration.screenWidthDp.dp.toPx() * 0.6f) + ) + + DpSize( + width = (width * scaler).toDp(), + height = (height * scaler).toDp(), + ) + } +} + + +private fun Int.scalerFor(max: Float): Float { + return max / this +} + + +@Composable +private fun Footer(event: Event, bubble: BubbleMeta, status: @Composable () -> Unit) { + Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(top = 2.dp)) { + val editedPrefix = if (event.edited) "(edited) " else null + Text( + fontSize = 9.sp, + text = "${editedPrefix ?: ""}${event.time}", + textAlign = TextAlign.End, + color = bubble.textColor(), + modifier = Modifier.wrapContentSize() + ) + status() + } +} + +@Composable +private fun TextContent(bubble: BubbleMeta, text: String) { + Text( + text = text, + color = bubble.textColor(), + fontSize = 15.sp, + modifier = Modifier.wrapContentSize(), + textAlign = TextAlign.Start, + ) +} + +@Composable +private fun AuthorName(event: Event, bubble: BubbleMeta) { + Text( + fontSize = 11.sp, + text = event.authorName, + maxLines = 1, + color = bubble.textColor() + ) +} + @Composable private fun BubbleMeta.textColor(): Color { return if (this.isSelf) SmallTalkTheme.extendedColors.onSelfBubble else SmallTalkTheme.extendedColors.onOthersBubble diff --git a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerScreen.kt b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerScreen.kt index d5476fe..8d0ae15 100644 --- a/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerScreen.kt +++ b/features/messenger/src/main/kotlin/app/dapk/st/messenger/MessengerScreen.kt @@ -198,67 +198,19 @@ private fun ColumnScope.RoomContent(self: UserId, state: RoomState, replyActions val event = Event(item.author.displayName ?: item.author.id.value, item.edited, item.time) val status = @Composable { SendStatus(item) } when (item) { - is RoomEvent.Image -> MessageImage(this, item) - is RoomEvent.Message -> TextBubbleContent(this, event, item.content, status = status) - is RoomEvent.Reply -> ReplyBubbleContent(this, item) - is RoomEvent.Encrypted -> EncryptedBubbleContent(this, item) - } - } - } - } -} - -@Composable -private fun MessageImage(bubble: BubbleMeta, event: RoomEvent.Image) { - val context = LocalContext.current - - 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) - ) { - if (bubble.isNotSelf()) { - Text( - fontSize = 11.sp, - text = event.author.displayName ?: event.author.id.value, - maxLines = 1, - color = bubble.textColor() - ) - } - - Spacer(modifier = Modifier.height(4.dp)) - Image( - modifier = Modifier.size(event.imageMeta.scale(LocalDensity.current, LocalConfiguration.current)), - painter = rememberAsyncImagePainter( - model = ImageRequest.Builder(context) + is RoomEvent.Image -> { + val context = LocalContext.current + val imageRequest = ImageRequest.Builder(context) .fetcherFactory(LocalDecyptingFetcherFactory.current) - .memoryCacheKey(event.imageMeta.url) - .data(event) + .memoryCacheKey(item.imageMeta.url) + .data(item) .build() - ), - contentDescription = null, - ) - Spacer(modifier = Modifier.height(4.dp)) + ImageBubble(this, event, ImageContent(item.imageMeta.width, item.imageMeta.height, item.imageMeta.url), status, imageRequest) + } - Row(horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { - val editedPrefix = if (event.edited) "(edited) " else null - Text( - fontSize = 9.sp, - text = "${editedPrefix ?: ""}${event.time}", - textAlign = TextAlign.End, - color = bubble.textColor(), - modifier = Modifier.wrapContentSize() - ) - SendStatus(event) + is RoomEvent.Message -> TextBubble(this, event, item.content, status = status) + is RoomEvent.Reply -> ReplyBubble(this, item) + is RoomEvent.Encrypted -> EncryptedBubble(this, event, status) } } } @@ -292,55 +244,7 @@ private fun BubbleMeta.textColor(): Color { } @Composable -private fun EncryptedBubbleContent(bubble: BubbleMeta, event: RoomEvent.Encrypted) { - 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) - ) { - if (bubble.isNotSelf()) { - Text( - fontSize = 11.sp, - text = event.author.displayName ?: event.author.id.value, - maxLines = 1, - color = bubble.textColor() - ) - } - 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) - } - } - } - } -} - -@Composable -private fun ReplyBubbleContent(bubble: BubbleMeta, event: RoomEvent.Reply) { +private fun ReplyBubble(bubble: BubbleMeta, event: RoomEvent.Reply) { Box(modifier = Modifier.padding(start = 6.dp)) { Box( Modifier