Improves rendering of chat messages when scrolling.

This commit is contained in:
Vitor Pamplona
2023-06-21 10:23:49 -04:00
parent fbe0f584af
commit 6fe66986be
4 changed files with 137 additions and 84 deletions
@@ -502,35 +502,49 @@ private fun StatusRow(
onWantsToReply: (Note) -> Unit onWantsToReply: (Note) -> Unit
) { ) {
val grayTint = MaterialTheme.colors.placeholderText val grayTint = MaterialTheme.colors.placeholderText
val time = remember { baseNote.createdAt() ?: 0 }
Column() {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
ChatTimeAgo(time) ChatTimeAgo(baseNote)
RelayBadges(baseNote) RelayBadges(baseNote)
Spacer(modifier = DoubleHorzSpacer) Spacer(modifier = DoubleHorzSpacer)
} }
}
Column() {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
LikeReaction(baseNote, grayTint, accountViewModel) LikeReaction(baseNote, grayTint, accountViewModel)
Spacer(modifier = StdHorzSpacer) Spacer(modifier = StdHorzSpacer)
ZapReaction(baseNote, grayTint, accountViewModel) ZapReaction(baseNote, grayTint, accountViewModel)
Spacer(modifier = StdHorzSpacer) Spacer(modifier = StdHorzSpacer)
ReplyReaction(baseNote, grayTint, accountViewModel, showCounter = false, iconSize = Size16dp) { ReplyReaction(
baseNote,
grayTint,
accountViewModel,
showCounter = false,
iconSize = Size16dp
) {
onWantsToReply(baseNote) onWantsToReply(baseNote)
} }
} }
} }
}
@Composable @Composable
fun ChatTimeAgo(time: Long) { fun ChatTimeAgo(baseNote: Note) {
val context = LocalContext.current val context = LocalContext.current
val timeStr = remember { timeAgoShort(time, context = context) } val timeStr by remember(baseNote) {
derivedStateOf {
timeAgoShort(baseNote.createdAt() ?: 0, context = context)
}
}
Text( Text(
timeStr, text = timeStr,
color = MaterialTheme.colors.placeholderText, color = MaterialTheme.colors.placeholderText,
fontSize = 12.sp fontSize = 12.sp,
maxLines = 1
) )
} }
@@ -159,7 +159,6 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableSet import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import nostr.postr.toNpub import nostr.postr.toNpub
import java.io.File import java.io.File
import java.math.BigDecimal import java.math.BigDecimal
@@ -1282,8 +1281,14 @@ fun DisplayPeopleList(
members.take(3) members.take(3)
} }
val name by remember {
derivedStateOf {
"#${noteEvent.dTag()}"
}
}
Text( Text(
text = "#${noteEvent.dTag()}", text = name,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
@@ -1385,7 +1390,7 @@ private fun RenderBadgeAward(
} }
if (awardees.size > 100) { if (awardees.size > 100) {
Text(" and ${awardees.size - 100} others") Text(" and ${awardees.size - 100} others", maxLines = 1)
} }
} }
@@ -1427,7 +1432,8 @@ private fun RenderReaction(
if (note.event?.content() == "+") "" else note.event?.content() ?: "" if (note.event?.content() == "+") "" else note.event?.content() ?: ""
Text( Text(
text = refactorReactionText text = refactorReactionText,
maxLines = 1
) )
} }
@@ -1778,7 +1784,8 @@ private fun FirstUserInfoRow(
Text( Text(
" ${stringResource(id = R.string.boosted)}", " ${stringResource(id = R.string.boosted)}",
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = MaterialTheme.colors.placeholderText color = MaterialTheme.colors.placeholderText,
maxLines = 1
) )
} else { } else {
DisplayFollowingHashtagsInPost(eventNote, accountViewModel, nav) DisplayFollowingHashtagsInPost(eventNote, accountViewModel, nav)
@@ -1832,7 +1839,7 @@ fun TimeAgo(time: Long) {
} }
Text( Text(
timeStr, text = timeStr,
color = MaterialTheme.colors.placeholderText, color = MaterialTheme.colors.placeholderText,
maxLines = 1 maxLines = 1
) )
@@ -1990,37 +1997,41 @@ fun DisplayHighlight(
nav nav
) )
var userBase by remember { mutableStateOf<User?>(null) } authorHex?.let {
DisplayQuoteAuthor(authorHex, url, nav)
}
}
@Composable
@OptIn(ExperimentalLayoutApi::class)
private fun DisplayQuoteAuthor(
authorHex: String,
url: String?,
nav: (String) -> Unit
) {
var userBase by remember { mutableStateOf<User?>(LocalCache.getUserIfExists(authorHex)) }
if (userBase == null) {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
withContext(Dispatchers.IO) { launch(Dispatchers.IO) {
if (authorHex != null) {
userBase = LocalCache.checkGetOrCreateUser(authorHex) userBase = LocalCache.checkGetOrCreateUser(authorHex)
} }
} }
} }
FlowRow() { Row() {
authorHex?.let { authorHex ->
userBase?.let { userBase -> userBase?.let { userBase ->
val userState by userBase.live().metadata.observeAsState() LoadAndDisplayUser(userBase, nav)
val route = remember { "User/${userBase.pubkeyHex}" }
val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() }
val userTags = remember(userState) { userState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists() }
if (userDisplayName != null) {
CreateClickableTextWithEmoji(
clickablePart = userDisplayName,
suffix = " ",
tags = userTags,
route = route,
nav = nav
)
}
}
} }
url?.let { url -> url?.let { url ->
LoadAndDisplayUrl(url)
}
}
}
@Composable
private fun LoadAndDisplayUrl(url: String) {
val validatedUrl = remember { val validatedUrl = remember {
try { try {
URL(url) URL(url)
@@ -2031,10 +2042,30 @@ fun DisplayHighlight(
} }
validatedUrl?.host?.let { host -> validatedUrl?.host?.let { host ->
Text("on ") Text(remember { "on " }, maxLines = 1)
ClickableUrl(urlText = host, url = url) ClickableUrl(urlText = host, url = url)
} }
} }
@Composable
private fun LoadAndDisplayUser(
userBase: User,
nav: (String) -> Unit
) {
val userState by userBase.live().metadata.observeAsState()
val route = remember { "User/${userBase.pubkeyHex}" }
val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() }
val userTags =
remember(userState) { userState?.user?.info?.latestMetadata?.tags?.toImmutableListOfLists() }
if (userDisplayName != null) {
CreateClickableTextWithEmoji(
clickablePart = userDisplayName,
suffix = " ",
tags = userTags,
route = route,
nav = nav
)
} }
} }
@@ -2071,7 +2102,8 @@ fun DisplayFollowingHashtagsInPost(
color = MaterialTheme.colors.primary.copy( color = MaterialTheme.colors.primary.copy(
alpha = 0.52f alpha = 0.52f
) )
) ),
maxLines = 1
) )
} }
} }
@@ -2120,7 +2152,8 @@ fun DisplayPoW(
alpha = 0.52f alpha = 0.52f
), ),
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold,
maxLines = 1
) )
} }
@@ -2214,7 +2247,8 @@ private fun RenderPledgeAmount(
Text( Text(
text = reward, text = reward,
color = MaterialTheme.colors.placeholderText color = MaterialTheme.colors.placeholderText,
maxLines = 1
) )
} }
@@ -2421,7 +2455,7 @@ fun AudioTrackHeader(noteEvent: AudioTrackEvent, accountViewModel: AccountViewMo
var participantUsers by remember { mutableStateOf<List<Pair<Participant, User>>>(emptyList()) } var participantUsers by remember { mutableStateOf<List<Pair<Participant, User>>>(emptyList()) }
LaunchedEffect(key1 = participants) { LaunchedEffect(key1 = participants) {
withContext(Dispatchers.IO) { launch(Dispatchers.IO) {
participantUsers = participants.mapNotNull { part -> LocalCache.checkGetOrCreateUser(part.key)?.let { Pair(part, it) } } participantUsers = participants.mapNotNull { part -> LocalCache.checkGetOrCreateUser(part.key)?.let { Pair(part, it) } }
} }
} }
@@ -2925,7 +2959,7 @@ private fun FollowingIcon(iconSize: Dp) {
val myIconBackgroundModifier = remember { val myIconBackgroundModifier = remember {
Modifier Modifier
.clip(CircleShape) .clip(CircleShape)
.fillMaxSize(0.6f) .size(iconSize.times(0.6f))
.drawBehind { drawRect(backgroundColor) } .drawBehind { drawRect(backgroundColor) }
} }
@@ -62,7 +62,8 @@ private fun UserNameDisplay(
CreateTextWithEmoji( CreateTextWithEmoji(
text = bestDisplayName, text = bestDisplayName,
tags = tags, tags = tags,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold,
maxLines = 1
) )
CreateTextWithEmoji( CreateTextWithEmoji(
text = remember { "@$bestUserName" }, text = remember { "@$bestUserName" },
@@ -656,43 +656,47 @@ private fun LiveChannelActionOptions(
} }
} }
val accountState by accountViewModel.accountLiveData.observeAsState() val isLive by remember(channel) {
val isFollowing by remember(accountState) {
derivedStateOf { derivedStateOf {
accountState?.account?.followingChannels?.contains(channel.idHex) ?: false channel.info?.status() == "live"
}
}
val status by remember {
derivedStateOf {
channel.info?.status()
} }
} }
if (isMe) { if (isMe) {
// EditButton(accountViewModel, channel) // EditButton(accountViewModel, channel)
} else { } else {
LocalCache.addressables[channel.idHex]?.let { val note = remember(channel.idHex) {
if (status == "live") { LocalCache.getNoteIfExists(channel.idHex)
Text( }
text = "LIVE",
color = Color.White, note?.let {
fontWeight = FontWeight.Bold, if (isLive) {
modifier = Modifier LiveFlag()
.clip(SmallBorder)
.drawBehind { drawRect(Color.Red) }
.padding(horizontal = 5.dp)
)
Spacer(modifier = StdHorzSpacer) Spacer(modifier = StdHorzSpacer)
} }
LikeReaction(it, MaterialTheme.colors.onSurface, accountViewModel) LikeReaction(baseNote = it, grayTint = MaterialTheme.colors.onSurface, accountViewModel = accountViewModel)
Spacer(modifier = StdHorzSpacer) Spacer(modifier = StdHorzSpacer)
ZapReaction(baseNote = it, grayTint = MaterialTheme.colors.onSurface, accountViewModel = accountViewModel) ZapReaction(baseNote = it, grayTint = MaterialTheme.colors.onSurface, accountViewModel = accountViewModel)
} }
} }
} }
@Composable
private fun LiveFlag() {
Text(
text = "LIVE",
color = Color.White,
fontWeight = FontWeight.Bold,
modifier = remember {
Modifier
.clip(SmallBorder)
.drawBehind { drawRect(Color.Red) }
.padding(horizontal = 5.dp)
}
)
}
@Composable @Composable
private fun NoteCopyButton( private fun NoteCopyButton(
note: Channel note: Channel