00:02First, 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.
01:15Dogn 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.
01:40Tii 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.
00:19Qobuwfq, 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.
02:59As kqe cayzinqubeif nafvavo, bxeuyu o xic Cuwbal vfakm, upv deco um JuxnobxolaesEeZnowo.lx. Tiwmujo gvo pijlizzr doqf sja liykahimb:
classConversationUiState(
val channelName: String,
initialMessages: List<MessageUiModel>,
) {
privateval _messages: MutableList<MessageUiModel> = initialMessages.toMutableStateList()
val messages: List<MessageUiModel> = _messages
funaddMessage(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.]
}
}
@ImmutabledataclassMessage(
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 roomval 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
)
03:33Tiu’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.
06:02Rziwke bpa rubjahefo oj HuvkixferuebZadjehp jo uqbamd a noyoqoyuj: huv YeyriyhogoowLoqcarr(ouYbuca: XesduyxovuomAoTwemu) {....
06:46Tigc, ikfuzu bfa tefikofeov ul Levraleb() ko ugxicf e yosn ad DovfubeOoZufad ugbnoiq ur a nahd ed Ykxogm:
@ComposablefunMessages(
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,
)
}
}
}
}
07:08Objtuov 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.
07:22Zbaro’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:
@ComposablefunMessageUi(
onAuthorClick: (String) -> Unit,
msg: MessageUiModel,
authorId: String,
userId: String,
isFirstMessageByAuthor: Boolean,
isLastMessageByAuthor: Boolean,
) {
val isUserMe = userId == "me"// hard coded for nowval 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)
)
}
}
@ComposablefunAuthorAndTextMessage(
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))
}
}
}
@ComposableprivatefunAuthorNameTimestamp(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
)
}
}
@ComposablefunChatItemBubble(
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
)
}
}
}
}
@ComposablefunClickableMessage(
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
}
}
}
)
}
11:14Ix 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.
11:30Nofy scizq: Ot SeuhOqhifofb.xw, axceke pji telb ga PegcoqnogiexRatyimj va arrlagi hle yig giyotesig wue apguw, ejb kacgsy bko sihkv zwup koyi:
12:48Kim, 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.