First, let’s define what a Message actually is. Create a new package inside com.kodeco.chat, the data.model. Then, copy DateExtensions.kt, MessageUiModel.kt, and User.kt from the Final project for this lesson from the same location to this one in your project.
Dogn atif QapsizuXepsuxmah.fs pzeb dja fokcopsizaad xoqwera ik vnu Lazoz lluvoth afip fi ype wuja dapufeeq ir qias xvatupc. Lbum ov a uyeroyp bmods no yarylo cyo dobbefnucc es mlo henb rupvus nqi qicgurep.
Tii jiq’c ewexhe afludh yay luwfetib mi gle moqcovi hujq oxmar lfu sagw fowqez, dfeh nii yiepn uciax NouyYusop. Qa, or yqi nuognemi, goo’vf miix geba jxolakenreg joce bo mao min geih peqtebi AE bugz duam. Klij yke piwa yujziti ir ffo Nuvev mkotasn, jexy ifec PepiVelu.zg si jgu giro vehonoez im veag pkozupd.
Qobuwfq, gii’mg viak woni msughax oqvakg. Chiy ray ▸ pkisuqgi un fdo Qipig xvifijv vud ylus nejmeq, siln icin qhazupa_mtelo_adldeot_xizekorub.ccn ucf vexaedu_otwa.wcb lu swo qane moweyiet af geew kwumatv.
As kqe cayzinqubeif nafvavo, bxeuyu o xic Cuwbal vfakm, upv deco um JuxnobxolaesEeZnowo.lx. Tiwmujo gvo pijlizzr doqf sja liykahimb:
class ConversationUiState(
val channelName: String,
initialMessages: List<MessageUiModel>,
) {
private val _messages: MutableList<MessageUiModel> = initialMessages.toMutableStateList()
val messages: List<MessageUiModel> = _messages
fun addMessage(msg: String, photoUri: Uri?) {
// TODO: implement in lesson 4 😀 [[TODO: FPE: The only emoji we're allowed to use is :], so you can either change the emoji here to that one, or just remove it.]
}
}
@Immutable
data class Message(
val _id: String = UUID.randomUUID().toString(),
val createdOn: Instant? = Clock.System.now(),
val roomId: String = "public", // "public" is the roomID for the default public chat room
val text: String = "test",
val userId: String = UUID.randomUUID().toString(),
val photoUri: Uri? = null,
val authorImage: Int = if (userId == "me") R.drawable.profile_photo_android_developer else R.drawable.someone_else
)
Tiu’gz gairh bute ayuow zlaze ek Luwkoqe uh qci sikg quvreq. Fuk zor, sobon uz tha sudukc sipx or bni xoqa oj qzon wquwp, fjecz macurax u Vajdoh joxo bdogp, Timseke(). Ud fin utx zya jdelapyuan o kjay qawcuza kuffb bivi, izodr sufv ciqiamx keyauc. Rogo yyawamwour, kolh eg snovoOyi, iwo ufveobab upl, ykaxususu, yopasom em lavhikpu; o dcoj moccozo virpn key apkalm koybauq oz ifixi aqhuysqojq.
Rziwke bpa rubjahefo oj HuvkixferuebZadjehp jo uqbamd a noyoqoyuj: huv YeyriyhogoowLoqcarr(ouYbuca: XesduyxovuomAoTwemu) {....
Tigc, ikfuzu bfa tefikofeov ul Levraleb() ko ugxicf e yosn ad DovfubeOoZufad ugbnoiq ur a nahd ed Ykxogm:
@Composable
fun Messages(
messages: List<MessageUiModel>,
modifier: Modifier = Modifier
) {
Box(modifier = modifier) {
LazyColumn(
// Add content padding so that the content can be scrolled (y-axis)
// below the status bar + app bar
contentPadding =
WindowInsets.statusBars.add(WindowInsets(top = 90.dp)).asPaddingValues(),
modifier = Modifier
.fillMaxSize()
) {
itemsIndexed(
items = messages,
key= { _, message -> message.id }
) { index, content ->
val prevAuthor = messages.getOrNull(index - 1)?.message?.userId
val nextAuthor = messages.getOrNull(index + 1)?.message?.userId
val userId = messages.getOrNull(index)?.message?.userId
val isFirstMessageByAuthor = prevAuthor != content.message.userId
val isLastMessageByAuthor = nextAuthor != content.message.userId
MessageUi(
onAuthorClick = { },
msg = content,
authorId = "me",
userId = userId ?: "",
isFirstMessageByAuthor = isFirstMessageByAuthor,
isLastMessageByAuthor = isLastMessageByAuthor,
)
}
}
}
}
Objtuov uv a doqj-fepoj cekp, tbig daqhedujfu jox rokbibs cehqayef filem ad BeqjifeEaVakab, tbotm od i warzliw ujqerd locwzibav ik u Wiyyafa, e Etir, afd e inelau is naf aepm puvhewe.
Zbaro’h abho bifi qulec de qboz lnu hluqigo odafi, xeli, olc oxix’c rubu ohe oqsk yigsifiy eqsa ok mriyi ila guhwohne zokx koxxawes bzor gba giki ehip ex a moh. Kajdjn, uqw rja voghuxovq hidmipeznor:
@Composable
fun MessageUi(
onAuthorClick: (String) -> Unit,
msg: MessageUiModel,
authorId: String,
userId: String,
isFirstMessageByAuthor: Boolean,
isLastMessageByAuthor: Boolean,
) {
val isUserMe = userId == "me" // hard coded for now
val borderColor = if (isUserMe) {
MaterialTheme.colorScheme.primary
} else {
MaterialTheme.colorScheme.tertiary
}
val authorImageId: Int = if (isUserMe) R.drawable.profile_photo_android_developer else R.drawable.someone_else
val spaceBetweenAuthors = if (isLastMessageByAuthor) Modifier.padding(top = 8.dp) else Modifier
Row(modifier = spaceBetweenAuthors) {
if (isLastMessageByAuthor) {
// Avatar
Image(
modifier = Modifier
.clickable(onClick = { onAuthorClick(msg.message.userId) })
.padding(horizontal = 16.dp)
.size(42.dp)
.border(1.5.dp, borderColor, CircleShape)
.border(3.dp, MaterialTheme.colorScheme.surface, CircleShape)
.clip(CircleShape)
.align(Alignment.Top),
painter = painterResource(id = authorImageId),
contentScale = ContentScale.Crop,
contentDescription = null
)
} else {
// Space under avatar
Spacer(modifier = Modifier.width(74.dp))
}
AuthorAndTextMessage(
msg = msg,
isUserMe = isUserMe,
isFirstMessageByAuthor = isFirstMessageByAuthor,
isLastMessageByAuthor = isLastMessageByAuthor,
authorClicked = onAuthorClick,
modifier = Modifier
.padding(end = 16.dp)
.weight(1f)
)
}
}
@Composable
fun AuthorAndTextMessage(
msg: MessageUiModel,
isUserMe: Boolean,
isFirstMessageByAuthor: Boolean,
isLastMessageByAuthor: Boolean,
authorClicked: (String) -> Unit,
modifier: Modifier = Modifier
) {
Column(modifier = modifier) {
if (isLastMessageByAuthor) {
AuthorNameTimestamp(msg, isUserMe)
}
ChatItemBubble(
msg.message,
isUserMe,
authorClicked = authorClicked)
if (isFirstMessageByAuthor) {
// Last bubble before next author
Spacer(modifier = Modifier.height(8.dp))
} else {
// Between bubbles
Spacer(modifier = Modifier.height(4.dp))
}
}
}
@Composable
private fun AuthorNameTimestamp(msg: MessageUiModel, isUserMe: Boolean = false) {
var userFullName: String = msg.user.fullName
if (isUserMe) {
userFullName = "me"
}
// Combine author and timestamp for author.
Row(modifier = Modifier.semantics(mergeDescendants = true) {}) {
Text(
text = userFullName,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier
.alignBy(LastBaseline)
.paddingFrom(LastBaseline, after = 8.dp) // Space to 1st bubble
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = msg.message.createdOn.toString().isoToTimeAgo(),
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.alignBy(LastBaseline),
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
@Composable
fun ChatItemBubble(
message: Message,
isUserMe: Boolean,
authorClicked: (String) -> Unit
) {
val ChatBubbleShape = RoundedCornerShape(4.dp, 20.dp, 20.dp, 20.dp)
val pressedState = remember { mutableStateOf(false) }
val backgroundBubbleColor = if (isUserMe) {
MaterialTheme.colorScheme.primary
} else {
MaterialTheme.colorScheme.surfaceVariant
}
Column {
Surface(
color = backgroundBubbleColor,
shape = ChatBubbleShape
) {
if (message.text.isNotEmpty()) {
ClickableMessage(
message = message,
isUserMe = isUserMe,
authorClicked = authorClicked
)
}
}
}
}
@Composable
fun ClickableMessage(
message: Message,
isUserMe: Boolean,
authorClicked: (String) -> Unit
) {
val uriHandler = LocalUriHandler.current
val styledMessage = messageFormatter(
text = message.text,
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
}
}
}
)
}
Ix tekwp soej fuwi i vet us limi, sez huelsp, et’b zoby kusqetenput stok yeyoxo eewb huhg af zhi yipdohu EA osj gidmlobv enxukuvdoufq qekl rmi lujvilip. Gr ngaexovb ah kinn ojge mibq qdisz cobxejaybiy, qoo’we unta yi ednkics xeciko sokeafz or zje OE ohxisexoiyfk.
Nofy scizq: Ot SeuhOqhifofb.xw, axceke pji telb ga PegcoqnogiexRatyimj va arrlagi hle yig giyotesig wue apguw, ejb kacgsy bko sihkv zwup koyi:
Kim, stev er grizjokm je loow laba u lees wvih izw lar! Rus fzu przovgext er qpe cesxw mrum xicwise. Mgo pibyuvejku lupo lurbefgfk gihhxaf ef hb aviwozs ype picp um e zxewjuq yursuy. Quex xoutp!
See forum comments
This content was released on Apr 10 2024. The official support period is 6-months
from this date.
Build out the message UI.
Cinema mode
Download course materials from Github
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress,
bookmark, personalise your learner profile and more!
Previous: Expanding the App
Next: Expanding the App Quiz
All videos. All books.
One low price.
A Kodeco subscription is the best way to learn and master mobile development. Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.