Moving clickable to Row and not AsyncImage because it's faster

This commit is contained in:
Vitor Pamplona
2023-05-06 19:19:57 -04:00
parent fdca0af97b
commit bba45dd0a7
3 changed files with 162 additions and 110 deletions
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.note
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -54,7 +55,6 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
val noteEvent = note?.event
var popupExpanded by remember { mutableStateOf(false) } var popupExpanded by remember { mutableStateOf(false) }
if (note == null) { if (note == null) {
@@ -160,12 +160,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
} }
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
Box( Spacer(modifier = Modifier.width(65.dp))
modifier = Modifier
.width(65.dp)
.padding(0.dp)
) {
}
NoteCompose( NoteCompose(
baseNote = multiSetCard.note, baseNote = multiSetCard.note,
@@ -216,14 +211,16 @@ private fun AuthorPictureAndComment(
var content by remember { mutableStateOf<Pair<User, String?>>(Pair(author, null)) } var content by remember { mutableStateOf<Pair<User, String?>>(Pair(author, null)) }
LaunchedEffect(key1 = zapRequest.idHex) { LaunchedEffect(key1 = zapRequest.idHex) {
(zapRequest.event as? LnZapRequestEvent)?.let { withContext(Dispatchers.IO) {
val decryptedContent = accountViewModel.decryptZap(zapRequest) (zapRequest.event as? LnZapRequestEvent)?.let {
if (decryptedContent != null) { val decryptedContent = accountViewModel.decryptZap(zapRequest)
val author = LocalCache.getOrCreateUser(decryptedContent.pubKey) if (decryptedContent != null) {
content = Pair(author, decryptedContent.content) val author = LocalCache.getOrCreateUser(decryptedContent.pubKey)
} else { content = Pair(author, decryptedContent.content)
if (!zapRequest.event?.content().isNullOrBlank()) { } else {
content = Pair(author, zapRequest.event?.content()) if (!zapRequest.event?.content().isNullOrBlank()) {
content = Pair(author, zapRequest.event?.content())
}
} }
} }
} }
@@ -246,10 +243,14 @@ private fun AuthorPictureAndComment(
Modifier Modifier
} }
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) { Row(
modifier = modifier.clickable {
navController.navigate("User/${author.pubkeyHex}")
},
verticalAlignment = Alignment.CenterVertically
) {
FastNoteAuthorPicture( FastNoteAuthorPicture(
author = author, author = author,
navController = navController,
userAccount = accountUser, userAccount = accountUser,
size = 35.dp size = 35.dp
) )
@@ -294,7 +295,6 @@ fun AuthorGallery(
@Composable @Composable
fun FastNoteAuthorPicture( fun FastNoteAuthorPicture(
author: User, author: User,
navController: NavController,
userAccount: User, userAccount: User,
size: Dp, size: Dp,
pictureModifier: Modifier = Modifier pictureModifier: Modifier = Modifier
@@ -303,14 +303,12 @@ fun FastNoteAuthorPicture(
val user = userState?.user ?: return val user = userState?.user ?: return
val showFollowingMark = userAccount.isFollowingCached(user) || user === userAccount val showFollowingMark = userAccount.isFollowingCached(user) || user === userAccount
UserPicture( UserPicture(
userHex = user.pubkeyHex, userHex = user.pubkeyHex,
userPicture = user.profilePicture(), userPicture = user.profilePicture(),
showFollowingMark = showFollowingMark, showFollowingMark = showFollowingMark,
size = size, size = size,
modifier = pictureModifier, modifier = pictureModifier
onClick = {
navController.navigate("User/${user.pubkeyHex}")
}
) )
} }
@@ -42,6 +42,7 @@ import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.get import androidx.core.graphics.get
import androidx.navigation.NavController import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.compose.AsyncImagePainter
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.NotificationCache
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
@@ -99,7 +100,7 @@ fun NoteCompose(
Log.d("Time", "Note Compose in $elapsed for ${baseNote.idHex} ${baseNote.event?.kind()} ${baseNote.event?.content()?.split("\n")?.get(0)?.take(100)}") Log.d("Time", "Note Compose in $elapsed for ${baseNote.idHex} ${baseNote.event?.kind()} ${baseNote.event?.content()?.split("\n")?.get(0)?.take(100)}")
} }
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class, ExperimentalTime::class)
@Composable @Composable
fun NoteComposeInner( fun NoteComposeInner(
baseNote: Note, baseNote: Note,
@@ -199,12 +200,14 @@ fun NoteComposeInner(
} }
Column( Column(
modifier = modifier.combinedClickable( modifier = modifier
onClick = { .combinedClickable(
routeFor(note, loggedIn)?.let { navController.navigate(it) } onClick = {
}, routeFor(note, loggedIn)?.let { navController.navigate(it) }
onLongClick = { popupExpanded = true } },
).background(backgroundColor) onLongClick = { popupExpanded = true }
)
.background(backgroundColor)
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -222,20 +225,26 @@ fun NoteComposeInner(
modifier = Modifier modifier = Modifier
.padding(start = if (!isBoostedNote && !isQuotedNote) 10.dp else 0.dp) .padding(start = if (!isBoostedNote && !isQuotedNote) 10.dp else 0.dp)
) { ) {
FirstUserInfoRow( val (value, elapsed1) = measureTimedValue {
baseNote = baseNote, FirstUserInfoRow(
showAuthorPicture = isQuotedNote, baseNote = baseNote,
account = account, showAuthorPicture = isQuotedNote,
accountViewModel = accountViewModel, account = account,
navController = navController accountViewModel = accountViewModel,
) navController = navController
)
}
Log.d("Time", "$elapsed1 Line 1 of each post ${noteEvent.content()}")
if (noteEvent !is RepostEvent && !makeItShort && !isQuotedNote) { if (noteEvent !is RepostEvent && !makeItShort && !isQuotedNote) {
SecondUserInfoRow( val (value, elapsed2) = measureTimedValue {
note, SecondUserInfoRow(
account, note,
navController account,
) navController
)
}
Log.d("Time", "$elapsed2 Line 2 of each post")
} }
Spacer(modifier = Modifier.height(2.dp)) Spacer(modifier = Modifier.height(2.dp))
@@ -543,34 +552,46 @@ private fun RenderBadgeAward(
if (note.replyTo.isNullOrEmpty()) return if (note.replyTo.isNullOrEmpty()) return
val noteEvent = note.event as? BadgeAwardEvent ?: return val noteEvent = note.event as? BadgeAwardEvent ?: return
var awardees by remember { mutableStateOf<List<User>>(listOf()) }
Text(text = stringResource(R.string.award_granted_to)) Text(text = stringResource(R.string.award_granted_to))
/*
FlowRow(modifier = Modifier.padding(top = 5.dp)) { LaunchedEffect(key1 = note) {
noteEvent.awardees() withContext(Dispatchers.IO) {
.map { LocalCache.getOrCreateUser(it) } awardees = noteEvent.awardees().mapNotNull { hex ->
.forEach { LocalCache.checkGetOrCreateUser(hex)
UserPicture( }
user = it,
navController = navController,
userAccount = accountViewModel.userProfile(),
size = 35.dp
)
} }
} }
note.replyTo?.firstOrNull()?.let { FlowRow(modifier = Modifier.padding(top = 5.dp)) {
NoteCompose( awardees.forEach { user ->
it, Row(modifier = Modifier.clickable {
modifier = Modifier, navController.navigate("User/${user.pubkeyHex}")
isBoostedNote = false, },
isQuotedNote = true, verticalAlignment = Alignment.CenterVertically
unPackReply = false, ) {
parentBackgroundColor = backgroundColor, UserPicture(
accountViewModel = accountViewModel, baseUser = user,
navController = navController baseUserAccount = accountViewModel.userProfile(),
) size = 35.dp
} )
}
}
}
note.replyTo?.firstOrNull()?.let {
NoteCompose(
it,
modifier = Modifier,
isBoostedNote = false,
isQuotedNote = true,
unPackReply = false,
parentBackgroundColor = backgroundColor,
accountViewModel = accountViewModel,
navController = navController
)
}*/
ReactionsRow(note, accountViewModel, navController) ReactionsRow(note, accountViewModel, navController)
@@ -762,34 +783,30 @@ private fun FirstUserInfoRow(
navController: NavController navController: NavController
) { ) {
var moreActionsExpanded by remember { mutableStateOf(false) } var moreActionsExpanded by remember { mutableStateOf(false) }
val context = LocalContext.current.applicationContext val eventNote = baseNote.event ?: return
val time = baseNote.createdAt() ?: return
val loggedIn = account.userProfile()
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
if (showAuthorPicture) { if (showAuthorPicture) {
NoteAuthorPicture(baseNote, navController, account.userProfile(), 25.dp) NoteAuthorPicture(baseNote, navController, loggedIn, 25.dp)
Spacer(Modifier.padding(horizontal = 5.dp)) Spacer(Modifier.padding(horizontal = 5.dp))
NoteUsernameDisplay(baseNote, Modifier.weight(1f)) NoteUsernameDisplay(baseNote, Modifier.weight(1f))
} else { } else {
NoteUsernameDisplay(baseNote, Modifier.weight(1f)) NoteUsernameDisplay(baseNote, Modifier.weight(1f))
} }
if (baseNote.event is RepostEvent) { if (eventNote is RepostEvent) {
Text( Text(
" ${stringResource(id = R.string.boosted)}", " ${stringResource(id = R.string.boosted)}",
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
} else { } else {
baseNote.event?.let { DisplayFollowingHashtagsInPost(eventNote, account, navController)
DisplayFollowingHashtagsInPost(it, account, navController)
}
} }
Text( TimeAgo(time)
timeAgo(baseNote.createdAt(), context = context),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
maxLines = 1
)
IconButton( IconButton(
modifier = Modifier.size(24.dp), modifier = Modifier.size(24.dp),
@@ -812,6 +829,25 @@ private fun FirstUserInfoRow(
} }
} }
@Composable
fun TimeAgo(time: Long) {
val context = LocalContext.current
var timeStr by remember { mutableStateOf("") }
LaunchedEffect(key1 = time) {
withContext(Dispatchers.IO) {
timeStr = timeAgo(time, context = context)
}
}
Text(
timeStr,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
maxLines = 1
)
}
@Composable @Composable
private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavController) { private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavController) {
val baseChannel = baseNote.channel() val baseChannel = baseNote.channel()
@@ -963,7 +999,7 @@ fun DisplayFollowingHashtagsInPost(
) { ) {
var firstTag by remember { mutableStateOf<String?>(null) } var firstTag by remember { mutableStateOf<String?>(null) }
LaunchedEffect(key1 = noteEvent) { LaunchedEffect(key1 = noteEvent.id()) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
firstTag = noteEvent.firstIsTaggedHashes(account.followingTagSet()) firstTag = noteEvent.firstIsTaggedHashes(account.followingTagSet())
} }
@@ -1102,7 +1138,24 @@ fun DisplayReward(
fun BadgeDisplay(baseNote: Note) { fun BadgeDisplay(baseNote: Note) {
val background = MaterialTheme.colors.background val background = MaterialTheme.colors.background
val badgeData = baseNote.event as? BadgeDefinitionEvent ?: return val badgeData = baseNote.event as? BadgeDefinitionEvent ?: return
var backgroundFromImage by remember { mutableStateOf(background) }
val image = badgeData.image()
val name = badgeData.name()
val description = badgeData.description()
var backgroundFromImage by remember { mutableStateOf(Pair(background, background)) }
var imageResult by remember { mutableStateOf<AsyncImagePainter.State.Success?>(null) }
LaunchedEffect(key1 = imageResult) {
withContext(Dispatchers.IO) {
imageResult?.let {
val backgroundColor = it.result.drawable.toBitmap(200, 200).copy(Bitmap.Config.ARGB_8888, false).get(0, 199)
val colorFromImage = Color(backgroundColor)
val textBackground = if (colorFromImage.luminance() > 0.5) lightColors().onBackground else darkColors().onBackground
backgroundFromImage = Pair(colorFromImage, textBackground)
}
}
}
Row( Row(
modifier = Modifier modifier = Modifier
@@ -1113,26 +1166,25 @@ fun BadgeDisplay(baseNote: Note) {
MaterialTheme.colors.primary.copy(alpha = 0.32f), MaterialTheme.colors.primary.copy(alpha = 0.32f),
CutCornerShape(20) CutCornerShape(20)
) )
.background(backgroundFromImage) .background(backgroundFromImage.first)
) { ) {
Column { Column {
badgeData.image()?.let { image.let {
AsyncImage( AsyncImage(
model = it, model = it,
contentDescription = stringResource( contentDescription = stringResource(
R.string.badge_award_image_for, R.string.badge_award_image_for,
it name ?: ""
), ),
contentScale = ContentScale.FillWidth, contentScale = ContentScale.FillWidth,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
onSuccess = { onSuccess = {
val backgroundColor = it.result.drawable.toBitmap(200, 200).copy(Bitmap.Config.ARGB_8888, false).get(0, 199) imageResult = it
backgroundFromImage = Color(backgroundColor)
} }
) )
} }
badgeData.name()?.let { name?.let {
Text( Text(
text = it, text = it,
style = MaterialTheme.typography.body1, style = MaterialTheme.typography.body1,
@@ -1140,11 +1192,11 @@ fun BadgeDisplay(baseNote: Note) {
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(start = 10.dp, end = 10.dp), .padding(start = 10.dp, end = 10.dp),
color = if (backgroundFromImage.luminance() > 0.5) lightColors().onBackground else darkColors().onBackground color = backgroundFromImage.second
) )
} }
badgeData.description()?.let { description?.let {
Text( Text(
text = it, text = it,
style = MaterialTheme.typography.caption, style = MaterialTheme.typography.caption,
@@ -1499,6 +1551,7 @@ fun UserPicture(
} }
} }
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun UserPicture( fun UserPicture(
baseUser: User, baseUser: User,
@@ -1516,27 +1569,38 @@ fun UserPicture(
val showFollowingMark = accountUser.isFollowingCached(user) || user == accountUser val showFollowingMark = accountUser.isFollowingCached(user) || user == accountUser
UserPicture( Row(
userHex = user.pubkeyHex, modifier = Modifier
userPicture = user.profilePicture(), .run {
showFollowingMark = showFollowingMark, if (onClick != null && onLongClick != null) {
size = size, this.combinedClickable(
modifier = modifier, onClick = { onClick(user) },
onClick = onClick?.let { { it(user) } }, onLongClick = { onLongClick(user) }
onLongClick = onLongClick?.let { { it(user) } } )
) } else if (onClick != null) {
this.clickable(onClick = { onClick(user) })
} else {
this
}
}
) {
UserPicture(
userHex = user.pubkeyHex,
userPicture = user.profilePicture(),
showFollowingMark = showFollowingMark,
size = size,
modifier = modifier
)
}
} }
@OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun UserPicture( fun UserPicture(
userHex: String, userHex: String,
userPicture: String?, userPicture: String?,
showFollowingMark: Boolean, showFollowingMark: Boolean,
size: Dp, size: Dp,
modifier: Modifier = Modifier, modifier: Modifier = Modifier
onClick: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null
) { ) {
Box( Box(
Modifier Modifier
@@ -1552,16 +1616,6 @@ fun UserPicture(
.height(size) .height(size)
.clip(shape = CircleShape) .clip(shape = CircleShape)
.background(MaterialTheme.colors.background) .background(MaterialTheme.colors.background)
.run {
if (onClick != null && onLongClick != null) {
this.combinedClickable(onClick = onClick, onLongClick = onLongClick)
} else if (onClick != null) {
this.clickable(onClick = onClick)
} else {
this
}
}
) )
if (showFollowingMark) { if (showFollowingMark) {
@@ -175,7 +175,7 @@ private fun FeedLoaded(
) )
} }
} }
Log.d("Time", "${item.javaClass.simpleName} Feed in $elapsed") Log.d("Time", "${item.javaClass.simpleName} Feed in $elapsed ${item.id()}")
} }
} }
} }