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