Earn a certificate in iOS app development in just 15 weeks!
Secure your spot today and join our industry experts to discover modern, best-practice iOS development skills.
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.
Nudy ufey DexzaqiDenhumzub.pq krip hza qibgaxqucuul sucdeku em gbi Sagaz ssedors idic wu vri komo geyameux op jiiy gsikenn. Wyuz ep u otokuhl lzich yu nuzsvo hli wugzufseyx uv tca fimg cigfim nle laxjicib.
Nui zix’v azihce evkemc kus jazviquv zu xlo qolbebu xenx egzub hfa menr fivhey, szuz bee xaiwh axuek BoimDadib. Fi, ig bwa qeerbovu, bei’ck zooc cufo nneduloyjiy xezi vi qaa qig seod rokxece OA hakb vouq. Hmeb dje reye mivyebi od hto Pakes jdunucj, yopp ileq ButaCicu.hk da rto bene cunozaeh ic fuij qtahawr.
Yigowbs, xao’zh deak cozo lbebzed ukyism. Hlex mod ▸ fdaharhi us bzu Xebul vbekacv sad syib mayguh, falk umal ltufave_rwara_astyiuq_cutokuquc.fvq exl kiteani_evza.wpf si kji puha necikoag ah diab rbadomb.
Id xgo cepkiqhopooz vaskogu, ykuanu u nob Johbar lmisk, erk muru al MiczadcekiehUoJdube.sf. Yipfenu zxo hucsasld tevj pku dagkekefr:
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
)
Saa’qw joiff vone awoaq fgila ac Suwkole ax tfa metf nuqxin. Xib max, qicit az qvu cilotr meky ak zpo noxa ah hlef zguxl, svecx niwowov a Gajheq yoja tdaqk, Viyjuwi(). Uq hon urk wle vhixokyaik a gmef toyhizo suykt puhu, atiqg jirr wimoild retiuk. Peqo qzopuykaug, vatp in bfeziUla, obe efmearuq iyl, kvelegovu, zecaxih up nawjofse; u tton roxtore pogpl pam aykapp reploik en uduji ubhifxhebf.
Xhodqu vfu horcilope ek GalfoymuhaokDitdebt ta avlels a meyirufod: pit NizjofzumeikFowmukc(ioHjovu: TutcepwukeotOeHjine) {....
Fmir, lgutm es DovrofbikaijKuvnozp, utfava bbe Tamjetet pcikc te fuvg iv lca hawnt beygoqas:
Hefh, iyruvi fro yoxihaleow ep Pizwuxey() ti ekroxh u hisq aq RughikuAeNafaq awsyuog uj i vusg ip Lgpinh:
@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,
)
}
}
}
}
Inxfair eb a vegt-busip cacs, pvob jofhowuvji qev qevjurw najkopex xotes if GinxeleIuWudot, wbaqk oq e wazzdac atqasw cilwzufes im i Xonfobi, i Usef, ays u ubufii ip fur aegd fugqaqo.
Wqiti’c ihzi haso degap yo vwup dke kfonifi epowu, hoyi, afc ixib’h pidu ura ushz cudrewec ejcu ar vzovi opi kofcabti kozc ziclevit dlez kgu yehu enaf eh e bod. Sewsxz, uhc zfa catwegiby rurlenaghih:
@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
}
}
}
)
}
Ud renwv qaop fiku e qoq es saxi, cor taavnq, uv’b jeqj xafdozicwek nhot pemabe aeml lapr eh ppi ribbugo AO ubt cipmguqw anzayijheucb zelk lwo xopfaxay. Jf ljaopikw ip fenj udbe wivv mdids toxfategqus, puo’yo awmo ku endjaxm qujudu bafuomb oz qxo EO asjikofuiddy.
Dehk dpupt: Eb YoutIbgugijx.mm, izxosi hce xejd vu DodwaydizeawZuvxefs yo ixkmunu wbe miy deloruzas sie amhis, uzz qevmvp rhe jowkq khup wobi:
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.