From bba45dd0a7602b8bec754292237e29284a5ebe6f Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sat, 6 May 2023 19:19:57 -0400 Subject: [PATCH] Moving clickable to Row and not AsyncImage because it's faster --- .../amethyst/ui/note/MultiSetCompose.kt | 42 ++-- .../amethyst/ui/note/NoteCompose.kt | 228 +++++++++++------- .../amethyst/ui/screen/CardFeedView.kt | 2 +- 3 files changed, 162 insertions(+), 110 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt index a27c7e9df..38f4932b3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt @@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.note import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -54,7 +55,6 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return - val noteEvent = note?.event var popupExpanded by remember { mutableStateOf(false) } if (note == null) { @@ -160,12 +160,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun } Row(Modifier.fillMaxWidth()) { - Box( - modifier = Modifier - .width(65.dp) - .padding(0.dp) - ) { - } + Spacer(modifier = Modifier.width(65.dp)) NoteCompose( baseNote = multiSetCard.note, @@ -216,14 +211,16 @@ private fun AuthorPictureAndComment( var content by remember { mutableStateOf>(Pair(author, null)) } LaunchedEffect(key1 = zapRequest.idHex) { - (zapRequest.event as? LnZapRequestEvent)?.let { - val decryptedContent = accountViewModel.decryptZap(zapRequest) - if (decryptedContent != null) { - val author = LocalCache.getOrCreateUser(decryptedContent.pubKey) - content = Pair(author, decryptedContent.content) - } else { - if (!zapRequest.event?.content().isNullOrBlank()) { - content = Pair(author, zapRequest.event?.content()) + withContext(Dispatchers.IO) { + (zapRequest.event as? LnZapRequestEvent)?.let { + val decryptedContent = accountViewModel.decryptZap(zapRequest) + if (decryptedContent != null) { + val author = LocalCache.getOrCreateUser(decryptedContent.pubKey) + content = Pair(author, decryptedContent.content) + } else { + if (!zapRequest.event?.content().isNullOrBlank()) { + content = Pair(author, zapRequest.event?.content()) + } } } } @@ -246,10 +243,14 @@ private fun AuthorPictureAndComment( Modifier } - Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) { + Row( + modifier = modifier.clickable { + navController.navigate("User/${author.pubkeyHex}") + }, + verticalAlignment = Alignment.CenterVertically + ) { FastNoteAuthorPicture( author = author, - navController = navController, userAccount = accountUser, size = 35.dp ) @@ -294,7 +295,6 @@ fun AuthorGallery( @Composable fun FastNoteAuthorPicture( author: User, - navController: NavController, userAccount: User, size: Dp, pictureModifier: Modifier = Modifier @@ -303,14 +303,12 @@ fun FastNoteAuthorPicture( val user = userState?.user ?: return val showFollowingMark = userAccount.isFollowingCached(user) || user === userAccount + UserPicture( userHex = user.pubkeyHex, userPicture = user.profilePicture(), showFollowingMark = showFollowingMark, size = size, - modifier = pictureModifier, - onClick = { - navController.navigate("User/${user.pubkeyHex}") - } + modifier = pictureModifier ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 545650749..964045df6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -42,6 +42,7 @@ import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.get import androidx.navigation.NavController import coil.compose.AsyncImage +import coil.compose.AsyncImagePainter import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.NotificationCache 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)}") } -@OptIn(ExperimentalFoundationApi::class) +@OptIn(ExperimentalFoundationApi::class, ExperimentalTime::class) @Composable fun NoteComposeInner( baseNote: Note, @@ -199,12 +200,14 @@ fun NoteComposeInner( } Column( - modifier = modifier.combinedClickable( - onClick = { - routeFor(note, loggedIn)?.let { navController.navigate(it) } - }, - onLongClick = { popupExpanded = true } - ).background(backgroundColor) + modifier = modifier + .combinedClickable( + onClick = { + routeFor(note, loggedIn)?.let { navController.navigate(it) } + }, + onLongClick = { popupExpanded = true } + ) + .background(backgroundColor) ) { Row( modifier = Modifier @@ -222,20 +225,26 @@ fun NoteComposeInner( modifier = Modifier .padding(start = if (!isBoostedNote && !isQuotedNote) 10.dp else 0.dp) ) { - FirstUserInfoRow( - baseNote = baseNote, - showAuthorPicture = isQuotedNote, - account = account, - accountViewModel = accountViewModel, - navController = navController - ) + val (value, elapsed1) = measureTimedValue { + FirstUserInfoRow( + baseNote = baseNote, + showAuthorPicture = isQuotedNote, + account = account, + accountViewModel = accountViewModel, + navController = navController + ) + } + Log.d("Time", "$elapsed1 Line 1 of each post ${noteEvent.content()}") if (noteEvent !is RepostEvent && !makeItShort && !isQuotedNote) { - SecondUserInfoRow( - note, - account, - navController - ) + val (value, elapsed2) = measureTimedValue { + SecondUserInfoRow( + note, + account, + navController + ) + } + Log.d("Time", "$elapsed2 Line 2 of each post") } Spacer(modifier = Modifier.height(2.dp)) @@ -543,34 +552,46 @@ private fun RenderBadgeAward( if (note.replyTo.isNullOrEmpty()) return val noteEvent = note.event as? BadgeAwardEvent ?: return + var awardees by remember { mutableStateOf>(listOf()) } Text(text = stringResource(R.string.award_granted_to)) - - FlowRow(modifier = Modifier.padding(top = 5.dp)) { - noteEvent.awardees() - .map { LocalCache.getOrCreateUser(it) } - .forEach { - UserPicture( - user = it, - navController = navController, - userAccount = accountViewModel.userProfile(), - size = 35.dp - ) + /* + LaunchedEffect(key1 = note) { + withContext(Dispatchers.IO) { + awardees = noteEvent.awardees().mapNotNull { hex -> + LocalCache.checkGetOrCreateUser(hex) + } } - } + } - note.replyTo?.firstOrNull()?.let { - NoteCompose( - it, - modifier = Modifier, - isBoostedNote = false, - isQuotedNote = true, - unPackReply = false, - parentBackgroundColor = backgroundColor, - accountViewModel = accountViewModel, - navController = navController - ) - } + FlowRow(modifier = Modifier.padding(top = 5.dp)) { + awardees.forEach { user -> + Row(modifier = Modifier.clickable { + navController.navigate("User/${user.pubkeyHex}") + }, + verticalAlignment = Alignment.CenterVertically + ) { + UserPicture( + baseUser = user, + 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) @@ -762,34 +783,30 @@ private fun FirstUserInfoRow( navController: NavController ) { 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) { if (showAuthorPicture) { - NoteAuthorPicture(baseNote, navController, account.userProfile(), 25.dp) + NoteAuthorPicture(baseNote, navController, loggedIn, 25.dp) Spacer(Modifier.padding(horizontal = 5.dp)) NoteUsernameDisplay(baseNote, Modifier.weight(1f)) } else { NoteUsernameDisplay(baseNote, Modifier.weight(1f)) } - if (baseNote.event is RepostEvent) { + if (eventNote is RepostEvent) { Text( " ${stringResource(id = R.string.boosted)}", fontWeight = FontWeight.Bold, color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) ) } else { - baseNote.event?.let { - DisplayFollowingHashtagsInPost(it, account, navController) - } + DisplayFollowingHashtagsInPost(eventNote, account, navController) } - Text( - timeAgo(baseNote.createdAt(), context = context), - color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), - maxLines = 1 - ) + TimeAgo(time) IconButton( 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 private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavController) { val baseChannel = baseNote.channel() @@ -963,7 +999,7 @@ fun DisplayFollowingHashtagsInPost( ) { var firstTag by remember { mutableStateOf(null) } - LaunchedEffect(key1 = noteEvent) { + LaunchedEffect(key1 = noteEvent.id()) { withContext(Dispatchers.IO) { firstTag = noteEvent.firstIsTaggedHashes(account.followingTagSet()) } @@ -1102,7 +1138,24 @@ fun DisplayReward( fun BadgeDisplay(baseNote: Note) { val background = MaterialTheme.colors.background 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(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( modifier = Modifier @@ -1113,26 +1166,25 @@ fun BadgeDisplay(baseNote: Note) { MaterialTheme.colors.primary.copy(alpha = 0.32f), CutCornerShape(20) ) - .background(backgroundFromImage) + .background(backgroundFromImage.first) ) { Column { - badgeData.image()?.let { + image.let { AsyncImage( model = it, contentDescription = stringResource( R.string.badge_award_image_for, - it + name ?: "" ), contentScale = ContentScale.FillWidth, modifier = Modifier.fillMaxWidth(), onSuccess = { - val backgroundColor = it.result.drawable.toBitmap(200, 200).copy(Bitmap.Config.ARGB_8888, false).get(0, 199) - backgroundFromImage = Color(backgroundColor) + imageResult = it } ) } - badgeData.name()?.let { + name?.let { Text( text = it, style = MaterialTheme.typography.body1, @@ -1140,11 +1192,11 @@ fun BadgeDisplay(baseNote: Note) { modifier = Modifier .fillMaxWidth() .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 = it, style = MaterialTheme.typography.caption, @@ -1499,6 +1551,7 @@ fun UserPicture( } } +@OptIn(ExperimentalFoundationApi::class) @Composable fun UserPicture( baseUser: User, @@ -1516,27 +1569,38 @@ fun UserPicture( val showFollowingMark = accountUser.isFollowingCached(user) || user == accountUser - UserPicture( - userHex = user.pubkeyHex, - userPicture = user.profilePicture(), - showFollowingMark = showFollowingMark, - size = size, - modifier = modifier, - onClick = onClick?.let { { it(user) } }, - onLongClick = onLongClick?.let { { it(user) } } - ) + Row( + modifier = Modifier + .run { + if (onClick != null && onLongClick != null) { + this.combinedClickable( + onClick = { onClick(user) }, + onLongClick = { onLongClick(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 fun UserPicture( userHex: String, userPicture: String?, showFollowingMark: Boolean, size: Dp, - modifier: Modifier = Modifier, - onClick: (() -> Unit)? = null, - onLongClick: (() -> Unit)? = null + modifier: Modifier = Modifier ) { Box( Modifier @@ -1552,16 +1616,6 @@ fun UserPicture( .height(size) .clip(shape = CircleShape) .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) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt index f355c6fd9..b7d1344bb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt @@ -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()}") } } }