From 853c3bd5e75f54664a1f3365ce92901f47a56f3a Mon Sep 17 00:00:00 2001 From: Divyansh Kushwaha Date: Sun, 24 May 2026 13:02:26 +0530 Subject: [PATCH] Add "show more" to truncate long chat messages at 8 lines --- .../jetchat/conversation/Conversation.kt | 65 ++++++++++++++----- .../example/compose/jetchat/data/FakeData.kt | 16 +++++ Jetchat/app/src/main/res/values/strings.xml | 1 + 3 files changed, 66 insertions(+), 16 deletions(-) diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/Conversation.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/Conversation.kt index 14a48d1680..a87bd96336 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/Conversation.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/conversation/Conversation.kt @@ -66,6 +66,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -83,8 +84,11 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.example.compose.jetchat.FunctionalityNotAvailablePopup import com.example.compose.jetchat.R @@ -440,6 +444,7 @@ private fun AuthorNameTimestamp(msg: Message) { } private val ChatBubbleShape = RoundedCornerShape(4.dp, 20.dp, 20.dp, 20.dp) +private const val MessageCollapsedMaxLines = 8 @Composable fun DayHeader(dayString: String) { @@ -516,23 +521,51 @@ fun ClickableMessage(message: Message, isUserMe: Boolean, authorClicked: (String primary = isUserMe, ) - ClickableText( - text = styledMessage, - style = MaterialTheme.typography.bodyLarge.copy(color = LocalContentColor.current), - modifier = Modifier.padding(16.dp), - onClick = { - styledMessage - .getStringAnnotations(start = it, end = it) - .firstOrNull() - ?.let { annotation -> - when (annotation.tag) { - SymbolAnnotationType.LINK.name -> uriHandler.openUri(annotation.item) - SymbolAnnotationType.PERSON.name -> authorClicked(annotation.item) - else -> Unit + var isExpanded by rememberSaveable { mutableStateOf(false) } + var isOverflowing by remember { mutableStateOf(false) } + + Column { + ClickableText( + text = styledMessage, + style = MaterialTheme.typography.bodyLarge.copy(color = LocalContentColor.current), + modifier = Modifier.padding( + start = 16.dp, + end = 16.dp, + top = 16.dp, + bottom = if (isOverflowing && !isExpanded) 4.dp else 16.dp, + ), + maxLines = if (isExpanded) Int.MAX_VALUE else MessageCollapsedMaxLines, + overflow = TextOverflow.Ellipsis, + onTextLayout = { isOverflowing = it.hasVisualOverflow }, + onClick = { + styledMessage + .getStringAnnotations(start = it, end = it) + .firstOrNull() + ?.let { annotation -> + when (annotation.tag) { + SymbolAnnotationType.LINK.name -> uriHandler.openUri(annotation.item) + SymbolAnnotationType.PERSON.name -> authorClicked(annotation.item) + else -> Unit + } } - } - }, - ) + }, + ) + if (isOverflowing && !isExpanded) { + Text( + text = stringResource(id = R.string.show_more), + style = MaterialTheme.typography.bodyMedium, + color = if (isUserMe) { + MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.8f) + } else { + MaterialTheme.colorScheme.primary + }, + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .clickable(role = Role.Button) { isExpanded = true }, + textDecoration = TextDecoration.Underline + ) + } + } } @Preview diff --git a/Jetchat/app/src/main/java/com/example/compose/jetchat/data/FakeData.kt b/Jetchat/app/src/main/java/com/example/compose/jetchat/data/FakeData.kt index e019716ad4..ffde69927b 100644 --- a/Jetchat/app/src/main/java/com/example/compose/jetchat/data/FakeData.kt +++ b/Jetchat/app/src/main/java/com/example/compose/jetchat/data/FakeData.kt @@ -27,6 +27,22 @@ import com.example.compose.jetchat.data.EMOJIS.EMOJI_POINTS import com.example.compose.jetchat.profile.ProfileScreenState val initialMessages = listOf( + Message( + author = "me", + content = "One thing I keep coming back to with Compose is how much easier state management " + + "has become. In the old View world you'd have to manually sync your UI to the model " + + "(call setText, setVisibility, notifyDataSetChanged) and it was really easy to miss a " + + "spot and end up with stale UI. With Compose, you just describe what the UI should " + + "look like for a given state and the framework takes care of the rest.\n" + + "The mental shift to thinking in terms of unidirectional data flow took a bit of " + + "getting used to, but once it clicked everything felt a lot more predictable. " + + "ViewModel + StateFlow + collectAsStateWithLifecycle is my go-to pattern now. " + + "Recomposition is still something I have to reason about carefully — especially " + + "around derived state and lambdas capturing stale values — but the tooling keeps " + + "getting better. Layout Inspector showing recomposition counts has been a huge help " + + "for spotting unnecessary work.", + timestamp = "8:15 PM" + ), Message( "me", "Check it out!", diff --git a/Jetchat/app/src/main/res/values/strings.xml b/Jetchat/app/src/main/res/values/strings.xml index bc04038a7a..8dfd7bfbef 100644 --- a/Jetchat/app/src/main/res/values/strings.xml +++ b/Jetchat/app/src/main/res/values/strings.xml @@ -38,6 +38,7 @@ ◀ Swipe to cancel Emojis Stickers + Show more Message Edit Profile