Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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!",
Expand Down
1 change: 1 addition & 0 deletions Jetchat/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<string name="swipe_to_cancel_recording">&#x25C0; Swipe to cancel</string>
<string name="emojis_label">Emojis</string>
<string name="stickers_label">Stickers</string>
<string name="show_more">Show more</string>

<string name="message">Message</string>
<string name="edit_profile">Edit Profile</string>
Expand Down