diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt index c50d08163..06519d830 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RobohashAsyncImage.kt @@ -1,6 +1,7 @@ package com.vitorpamplona.amethyst.ui.components import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter @@ -30,24 +31,31 @@ fun RobohashAsyncImage( colorFilter: ColorFilter? = null, filterQuality: FilterQuality = DrawScope.DefaultFilterQuality ) { - with(LocalDensity.current) { - AsyncImage( - model = Robohash.imageRequest( - LocalContext.current, - robot, - Size(robotSize.roundToPx(), robotSize.roundToPx()) - ), - contentDescription = contentDescription, - modifier = modifier, - transform = transform, - onState = onState, - alignment = alignment, - contentScale = contentScale, - alpha = alpha, - colorFilter = colorFilter, - filterQuality = filterQuality + val context = LocalContext.current + val size = with(LocalDensity.current) { + robotSize.roundToPx() + } + + val imageRequest = remember(size, robot) { + Robohash.imageRequest( + context, + robot, + Size(size, size) ) } + + AsyncImage( + model = imageRequest, + contentDescription = contentDescription, + modifier = modifier, + transform = transform, + onState = onState, + alignment = alignment, + contentScale = contentScale, + alpha = alpha, + colorFilter = colorFilter, + filterQuality = filterQuality + ) } var imageErrors = setOf() @@ -120,7 +128,7 @@ fun RobohashAsyncImageProxy( colorFilter: ColorFilter? = null, filterQuality: FilterQuality = DrawScope.DefaultFilterQuality ) { - val proxy = model.proxyUrl() + val proxy = remember(model) { model.proxyUrl() } if (proxy == null) { RobohashAsyncImage( robot = robot, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt index 4e6fa71fe..36fe88205 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MessageSetCompose.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -34,18 +35,18 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable -fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { +fun MessageSetCompose(messageSetCard: MessageSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { val noteState by messageSetCard.note.live().metadata.observeAsState() - val note = noteState?.note + val note = remember(noteState) { noteState?.note } var popupExpanded by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() if (note == null) { - BlankNote(Modifier, isInnerNote) + BlankNote(Modifier) } else { - var isNew by remember { mutableStateOf(false) } + var isNew by remember { mutableStateOf(false) } LaunchedEffect(key1 = messageSetCard.createdAt()) { scope.launch(Dispatchers.IO) { @@ -66,44 +67,40 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal MaterialTheme.colors.background } - Column( - modifier = Modifier.background(backgroundColor).combinedClickable( - onClick = { - scope.launch { - routeFor( - note, - accountViewModel.userProfile() - )?.let { navController.navigate(it) } - } - }, - onLongClick = { popupExpanded = true } - ) - ) { - Row( - modifier = Modifier - .padding( - start = if (!isInnerNote) 12.dp else 0.dp, - end = if (!isInnerNote) 12.dp else 0.dp, - top = 10.dp + val columnModifier = remember { + Modifier + .background(backgroundColor) + .padding( + start = 12.dp, + end = 12.dp, + top = 10.dp + ) + .combinedClickable( + onClick = { + scope.launch { + routeFor( + note, + accountViewModel.userProfile() + )?.let { navController.navigate(it) } + } + }, + onLongClick = { popupExpanded = true } + ) + .fillMaxWidth() + } + + Column(columnModifier) { + Row(Modifier.fillMaxWidth()) { + Box(modifier = remember { Modifier.width(55.dp).padding(top = 5.dp, end = 5.dp) }) { + Icon( + painter = painterResource(R.drawable.ic_dm), + null, + modifier = remember { Modifier.size(16.dp).align(Alignment.TopEnd) }, + tint = MaterialTheme.colors.primary ) - ) { - // Draws the like picture outside the boosted card. - if (!isInnerNote) { - Box( - modifier = Modifier - .width(55.dp) - .padding(top = 5.dp) - ) { - Icon( - painter = painterResource(R.drawable.ic_dm), - null, - modifier = Modifier.size(16.dp).align(Alignment.TopEnd), - tint = MaterialTheme.colors.primary - ) - } } - Column(modifier = Modifier.padding(start = if (!isInnerNote) 10.dp else 0.dp)) { + Column(modifier = remember { Modifier.padding(start = 10.dp) }) { NoteCompose( baseNote = messageSetCard.note, routeForLastRead = null, 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 1aebd5efa..ffe5c5da6 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 @@ -19,6 +19,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Bolt import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf @@ -40,6 +41,7 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.model.UserState import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.screen.MultiSetCard @@ -52,19 +54,19 @@ import kotlinx.coroutines.launch @Composable fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { val noteState by multiSetCard.note.live().metadata.observeAsState() - val note = noteState?.note + val note = remember(noteState) { noteState?.note } val accountState by accountViewModel.accountLiveData.observeAsState() - val account = accountState?.account ?: return + val account = remember(accountState) { accountState?.account } var popupExpanded by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() - if (note == null) { + if (note == null || account == null) { BlankNote(Modifier, false) } else { - var isNew by remember { mutableStateOf(false) } + var isNew by remember { mutableStateOf(false) } LaunchedEffect(key1 = multiSetCard.createdAt()) { scope.launch(Dispatchers.IO) { @@ -84,112 +86,157 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun MaterialTheme.colors.background } - Column( - modifier = Modifier + val columnModifier = remember { + Modifier .background(backgroundColor) + .padding( + start = 12.dp, + end = 12.dp, + top = 10.dp + ) .combinedClickable( onClick = { scope.launch { - routeFor(note, account.userProfile())?.let { navController.navigate(it) } + routeFor( + note, + account.userProfile() + )?.let { navController.navigate(it) } } }, onLongClick = { popupExpanded = true } ) - ) { - Row( - modifier = Modifier - .padding( - start = 12.dp, - end = 12.dp, - top = 10.dp - ) - ) { - Column(Modifier.fillMaxWidth()) { - if (multiSetCard.zapEvents.isNotEmpty()) { - Row(Modifier.fillMaxWidth()) { - // Draws the like picture outside the boosted card. - Box( - modifier = Modifier - .width(55.dp) - .padding(0.dp) - ) { - Icon( - imageVector = Icons.Default.Bolt, - contentDescription = "Zaps", - tint = BitcoinOrange, - modifier = Modifier - .size(25.dp) - .align(Alignment.TopEnd) - ) - } + .fillMaxWidth() + } - AuthorGalleryZaps(multiSetCard.zapEvents, navController, account, accountViewModel) - } - } + val zapEvents = remember { multiSetCard.zapEvents } + val boostEvents = remember { multiSetCard.boostEvents } + val likeEvents = remember { multiSetCard.likeEvents } - if (multiSetCard.boostEvents.isNotEmpty()) { - Row(Modifier.fillMaxWidth()) { - Box( - modifier = Modifier - .width(55.dp) - .padding(end = 4.dp) - ) { - Icon( - painter = painterResource(R.drawable.ic_retweeted), - null, - modifier = Modifier - .size(18.dp) - .align(Alignment.TopEnd), - tint = Color.Unspecified - ) - } + Column(modifier = columnModifier) { + if (zapEvents.isNotEmpty()) { + RenderZapGallery(zapEvents, navController, account, accountViewModel) + } - AuthorGallery(multiSetCard.boostEvents, navController, account, accountViewModel) - } - } + if (boostEvents.isNotEmpty()) { + RenderBoostGallery(boostEvents, navController, account, accountViewModel) + } - if (multiSetCard.likeEvents.isNotEmpty()) { - Row(Modifier.fillMaxWidth()) { - Box( - modifier = Modifier - .width(55.dp) - .padding(end = 5.dp) - ) { - Icon( - painter = painterResource(R.drawable.ic_liked), - null, - modifier = Modifier - .size(16.dp) - .align(Alignment.TopEnd), - tint = Color.Unspecified - ) - } + if (likeEvents.isNotEmpty()) { + RenderLikeGallery(likeEvents, navController, account, accountViewModel) + } - AuthorGallery(multiSetCard.likeEvents, navController, account, accountViewModel) - } - } + Row(Modifier.fillMaxWidth()) { + Spacer(modifier = Modifier.width(65.dp)) - Row(Modifier.fillMaxWidth()) { - Spacer(modifier = Modifier.width(65.dp)) + NoteCompose( + baseNote = multiSetCard.note, + routeForLastRead = null, + modifier = Modifier.padding(top = 5.dp), + isBoostedNote = true, + parentBackgroundColor = backgroundColor, + accountViewModel = accountViewModel, + navController = navController + ) - NoteCompose( - baseNote = multiSetCard.note, - routeForLastRead = null, - modifier = Modifier.padding(top = 5.dp), - isBoostedNote = true, - parentBackgroundColor = backgroundColor, - accountViewModel = accountViewModel, - navController = navController - ) - - NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel) - } - } + NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel) } } } } +@Composable +private fun RenderLikeGallery( + likeEvents: List, + navController: NavController, + account: Account, + accountViewModel: AccountViewModel +) { + Row(Modifier.fillMaxWidth()) { + Box( + modifier = remember { + Modifier + .width(55.dp) + .padding(end = 5.dp) + } + ) { + Icon( + painter = painterResource(R.drawable.ic_liked), + null, + modifier = remember { + Modifier + .size(16.dp) + .align(Alignment.TopEnd) + }, + tint = Color.Unspecified + ) + } + + AuthorGallery(likeEvents, navController, account, accountViewModel) + } +} + +@Composable +private fun RenderZapGallery( + zapEvents: Map, + navController: NavController, + account: Account, + accountViewModel: AccountViewModel +) { + Row(Modifier.fillMaxWidth()) { + Box( + modifier = remember { + Modifier + .width(55.dp) + .padding(0.dp) + } + ) { + Icon( + imageVector = Icons.Default.Bolt, + contentDescription = "Zaps", + tint = BitcoinOrange, + modifier = remember { + Modifier + .size(25.dp) + .align(Alignment.TopEnd) + } + ) + } + + AuthorGalleryZaps(zapEvents, navController, account, accountViewModel) + } +} + +@Composable +private fun RenderBoostGallery( + boostEvents: List, + navController: NavController, + account: Account, + accountViewModel: AccountViewModel +) { + Row(Modifier.fillMaxWidth()) { + Box( + modifier = remember { + Modifier + .width(55.dp) + .padding(end = 4.dp) + } + ) { + Icon( + painter = painterResource(R.drawable.ic_retweeted), + null, + modifier = remember { + Modifier + .size(18.dp) + .align(Alignment.TopEnd) + }, + tint = Color.Unspecified + ) + } + + AuthorGallery(boostEvents, navController, account, accountViewModel) + } +} + @Composable fun AuthorGalleryZaps( authorNotes: Map, @@ -197,13 +244,16 @@ fun AuthorGalleryZaps( account: Account, accountViewModel: AccountViewModel ) { - val accountState by account.userProfile().live().follows.observeAsState() - val accountUser = accountState?.user ?: return + val accountState = account.userProfile().live().follows.observeAsState() + + val listToRender = remember { + authorNotes.keys.take(50) + } Column(modifier = Modifier.padding(start = 10.dp)) { FlowRow() { - authorNotes.forEach { - AuthorPictureAndComment(it.key, navController, accountUser, accountViewModel) + listToRender.forEach { + AuthorPictureAndComment(it, navController, accountState, accountViewModel) } } } @@ -213,7 +263,7 @@ fun AuthorGalleryZaps( private fun AuthorPictureAndComment( zapRequest: Note, navController: NavController, - accountUser: User, + accountUser: State, accountViewModel: AccountViewModel ) { val author = zapRequest.author ?: return @@ -245,19 +295,25 @@ private fun AuthorPictureAndComment( author: User, comment: String?, navController: NavController, - accountUser: User, + accountUser: State, accountViewModel: AccountViewModel ) { - val modifier = if (!comment.isNullOrBlank()) { - Modifier.fillMaxWidth() - } else { - Modifier + val modifier = remember(comment) { + if (!comment.isNullOrBlank()) { + Modifier + .fillMaxWidth() + .clickable { + navController.navigate("User/${author.pubkeyHex}") + } + } else { + Modifier.clickable { + navController.navigate("User/${author.pubkeyHex}") + } + } } Row( - modifier = modifier.clickable { - navController.navigate("User/${author.pubkeyHex}") - }, + modifier = modifier, verticalAlignment = Alignment.CenterVertically ) { FastNoteAuthorPicture( @@ -288,20 +344,22 @@ fun AuthorGallery( account: Account, accountViewModel: AccountViewModel ) { - val accountState by account.userProfile().live().follows.observeAsState() - val accountUser = accountState?.user ?: return + val accountState = account.userProfile().live().follows.observeAsState() + val listToRender = remember { + Pair( + authorNotes.take(50).mapNotNull { it.author }, + authorNotes.size + ) + } Column(modifier = Modifier.padding(start = 10.dp)) { FlowRow() { - authorNotes.take(50).forEach { - val author = it.author - if (author != null) { - AuthorPictureAndComment(author, null, navController, accountUser, accountViewModel) - } + listToRender.first.forEach { author -> + AuthorPictureAndComment(author, null, navController, accountState, accountViewModel) } - if (authorNotes.size > 50) { - Text(" and ${authorNotes.size - 50} others") + if (listToRender.second > 50) { + Text(" and ${listToRender.second - 50} others") } } } @@ -310,18 +368,26 @@ fun AuthorGallery( @Composable fun FastNoteAuthorPicture( author: User, - userAccount: User, + userAccount: State, size: Dp, pictureModifier: Modifier = Modifier ) { val userState by author.live().metadata.observeAsState() - val user = userState?.user ?: return + val profilePicture = remember(userState) { + userState?.user?.profilePicture() + } - val showFollowingMark = userAccount.isFollowingCached(user) || user === userAccount + val authorPubKey = remember { + author.pubkeyHex + } + + val showFollowingMark = remember(userAccount.value) { + userAccount.value?.user?.isFollowingCached(author) == true || (author === userAccount.value?.user) + } UserPicture( - userHex = user.pubkeyHex, - userPicture = user.profilePicture(), + userHex = authorPubKey, + userPicture = profilePicture, showFollowingMark = showFollowingMark, size = size, 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 5c6bdc959..0ed76233f 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 @@ -96,6 +96,7 @@ import com.vitorpamplona.amethyst.service.model.PollNoteEvent import com.vitorpamplona.amethyst.service.model.PrivateDmEvent import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.ReportEvent +import com.vitorpamplona.amethyst.service.model.ReportedKey import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent import com.vitorpamplona.amethyst.ui.components.ClickableUrl @@ -178,208 +179,226 @@ fun NoteComposeInner( navController: NavController ) { val accountState by accountViewModel.accountLiveData.observeAsState() - val account = accountState?.account ?: return - val loggedIn = account.userProfile() + val account = remember(accountState) { accountState?.account } ?: return + val loggedIn = remember(accountState) { accountState?.account?.userProfile() } ?: return val noteState by baseNote.live().metadata.observeAsState() - val note = noteState?.note + val note = remember(noteState) { noteState?.note } val noteReportsState by baseNote.live().reports.observeAsState() - val noteForReports = noteReportsState?.note ?: return - - var popupExpanded by remember { mutableStateOf(false) } - var showHiddenNote by remember { mutableStateOf(false) } - - var isAcceptableAndCanPreview by remember { mutableStateOf(Pair(true, true)) } - - LaunchedEffect(key1 = noteReportsState) { - withContext(Dispatchers.IO) { - val newCanPreview = note?.author === loggedIn || - (note?.author?.let { loggedIn.isFollowingCached(it) } ?: true) || - !noteForReports.hasAnyReports() - - val newIsAcceptable = account.isAcceptable(noteForReports) - - if (newIsAcceptable != isAcceptableAndCanPreview.first && newCanPreview != isAcceptableAndCanPreview.second) { - isAcceptableAndCanPreview = Pair(newIsAcceptable, newCanPreview) - } - } - } + val noteForReports = remember(noteReportsState) { noteReportsState?.note } ?: return val noteEvent = note?.event val baseChannel = note?.channel() + var popupExpanded by remember { mutableStateOf(false) } + if (noteEvent == null) { BlankNote( - modifier.combinedClickable( - onClick = { }, - onLongClick = { popupExpanded = true } - ), - isBoostedNote - ) - } else if (!isAcceptableAndCanPreview.first && !showHiddenNote) { - if (!account.isHidden(noteForReports.author!!)) { - HiddenNote( - account.getRelevantReports(noteForReports), - loggedIn, - modifier, - isBoostedNote, - navController, - onClick = { showHiddenNote = true } - ) - } - } else if ((noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) && baseChannel != null) { - ChannelHeader(baseChannel = baseChannel, account = account, navController = navController) - } else if (noteEvent is BadgeDefinitionEvent) { - BadgeDisplay(baseNote = note) - } else if (noteEvent is FileHeaderEvent) { - FileHeaderDisplay(note) - } else if (noteEvent is FileStorageHeaderEvent) { - FileStorageHeaderDisplay(note) - } else { - var isNew by remember { mutableStateOf(false) } - - val scope = rememberCoroutineScope() - - LaunchedEffect(key1 = routeForLastRead) { - scope.launch(Dispatchers.IO) { - routeForLastRead?.let { - val lastTime = NotificationCache.load(it) - - val createdAt = note.createdAt() - if (createdAt != null) { - NotificationCache.markAsRead(it, createdAt) - - val newIsNew = createdAt > lastTime - if (newIsNew != isNew) { - isNew = newIsNew - } - } - } - } - } - - val backgroundColor = if (isNew) { - val newColor = MaterialTheme.colors.primary.copy(0.12f) - if (parentBackgroundColor != null) { - newColor.compositeOver(parentBackgroundColor) - } else { - newColor.compositeOver(MaterialTheme.colors.background) - } - } else { - parentBackgroundColor ?: MaterialTheme.colors.background - } - - Column( - modifier = modifier - .combinedClickable( - onClick = { - scope.launch { - routeFor(note, loggedIn)?.let { navController.navigate(it) } - } - }, + remember { + modifier.combinedClickable( + onClick = { }, onLongClick = { popupExpanded = true } ) - .background(backgroundColor) - ) { - Row( - modifier = Modifier - .padding( - start = if (!isBoostedNote) 12.dp else 0.dp, - end = if (!isBoostedNote) 12.dp else 0.dp, - top = if (addMarginTop && !isBoostedNote) 10.dp else 0.dp - ) - ) { - if (!isBoostedNote && !isQuotedNote) { - DrawAuthorImages(baseNote, loggedIn, navController) + }, + isBoostedNote + ) + + note?.let { + NoteQuickActionMenu(it, popupExpanded, { popupExpanded = false }, accountViewModel) + } + } else { + var showHiddenNote by remember { mutableStateOf(false) } + var isAcceptableAndCanPreview by remember { mutableStateOf(Pair(true, true)) } + + LaunchedEffect(key1 = noteReportsState, key2 = accountState) { + withContext(Dispatchers.IO) { + account.userProfile().let { loggedIn -> + val newCanPreview = note.author === loggedIn || + (note.author?.let { loggedIn.isFollowingCached(it) } ?: true) || + !(noteForReports.hasAnyReports()) + + val newIsAcceptable = account.isAcceptable(noteForReports) + + if (newIsAcceptable != isAcceptableAndCanPreview.first && newCanPreview != isAcceptableAndCanPreview.second) { + isAcceptableAndCanPreview = Pair(newIsAcceptable, newCanPreview) + } } + } + } - Column( - modifier = Modifier - .padding(start = if (!isBoostedNote && !isQuotedNote) 10.dp else 0.dp) - ) { - FirstUserInfoRow( - baseNote = baseNote, - showAuthorPicture = isQuotedNote, - account = account, - accountViewModel = accountViewModel, - navController = navController + if (!isAcceptableAndCanPreview.first && !showHiddenNote) { + if (!account.isHidden(noteForReports.author!!)) { + HiddenNote( + account.getRelevantReports(noteForReports), + account.userProfile(), + modifier, + isBoostedNote, + navController, + onClick = { showHiddenNote = true } + ) + } + } else if ((noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) && baseChannel != null) { + ChannelHeader(baseChannel = baseChannel, account = account, navController = navController) + } else if (noteEvent is BadgeDefinitionEvent) { + BadgeDisplay(baseNote = note) + } else if (noteEvent is FileHeaderEvent) { + FileHeaderDisplay(note) + } else if (noteEvent is FileStorageHeaderEvent) { + FileStorageHeaderDisplay(note) + } else { + var isNew by remember { mutableStateOf(false) } + + val scope = rememberCoroutineScope() + + LaunchedEffect(key1 = routeForLastRead) { + scope.launch(Dispatchers.IO) { + routeForLastRead?.let { + val lastTime = NotificationCache.load(it) + + val createdAt = note.createdAt() + if (createdAt != null) { + NotificationCache.markAsRead(it, createdAt) + + val newIsNew = createdAt > lastTime + if (newIsNew != isNew) { + isNew = newIsNew + } + } + } + } + } + + val backgroundColor = if (isNew) { + val newColor = MaterialTheme.colors.primary.copy(0.12f) + if (parentBackgroundColor != null) { + newColor.compositeOver(parentBackgroundColor) + } else { + newColor.compositeOver(MaterialTheme.colors.background) + } + } else { + parentBackgroundColor ?: MaterialTheme.colors.background + } + + val columnModifier = remember(backgroundColor) { + modifier + .combinedClickable( + onClick = { + scope.launch { + routeFor(note, loggedIn)?.let { + navController.navigate(it) + } + } + }, + onLongClick = { popupExpanded = true } ) + .background(backgroundColor) + } - if (noteEvent !is RepostEvent && !makeItShort && !isQuotedNote) { - SecondUserInfoRow( - note, - account, - navController - ) + Column(modifier = columnModifier) { + Row( + modifier = remember { + Modifier + .padding( + start = if (!isBoostedNote) 12.dp else 0.dp, + end = if (!isBoostedNote) 12.dp else 0.dp, + top = if (addMarginTop && !isBoostedNote) 10.dp else 0.dp + ) + } + ) { + if (!isBoostedNote && !isQuotedNote) { + DrawAuthorImages(baseNote, loggedIn, navController) } - Spacer(modifier = Modifier.height(2.dp)) - - if (!makeItShort) { - ReplyRow( - note, - unPackReply, - backgroundColor, - account, - accountViewModel, - navController + Column( + modifier = remember { + Modifier + .padding(start = if (!isBoostedNote && !isQuotedNote) 10.dp else 0.dp) + } + ) { + FirstUserInfoRow( + baseNote = baseNote, + showAuthorPicture = isQuotedNote, + account = account, + accountViewModel = accountViewModel, + navController = navController ) - } - when (noteEvent) { - is ReactionEvent -> { - RenderReaction(note, backgroundColor, accountViewModel, navController) - } - - is RepostEvent -> { - RenderRepost(note, backgroundColor, accountViewModel, navController) - } - - is ReportEvent -> { - RenderReport(note) - } - - is LongTextNoteEvent -> { - RenderLongFormContent(note, loggedIn, accountViewModel, navController) - } - - is BadgeAwardEvent -> { - RenderBadgeAward(note, backgroundColor, accountViewModel, navController) - } - - is PrivateDmEvent -> { - RenderPrivateMessage(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, navController) - } - - is HighlightEvent -> { - RenderHighlight(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, navController) - } - - is PollNoteEvent -> { - RenderPoll( + if (noteEvent !is RepostEvent && !makeItShort && !isQuotedNote) { + SecondUserInfoRow( note, - makeItShort, - isAcceptableAndCanPreview.second, + account, + navController + ) + } + + Spacer(modifier = Modifier.height(2.dp)) + + if (!makeItShort) { + ReplyRow( + note, + unPackReply, backgroundColor, + account, accountViewModel, navController ) } - else -> { - RenderTextEvent( - note, - makeItShort, - isAcceptableAndCanPreview.second, - backgroundColor, - accountViewModel, - navController - ) - } - } + when (noteEvent) { + is ReactionEvent -> { + RenderReaction(note, backgroundColor, accountViewModel, navController) + } - NoteQuickActionMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel) + is RepostEvent -> { + RenderRepost(note, backgroundColor, accountViewModel, navController) + } + + is ReportEvent -> { + RenderReport(note) + } + + is LongTextNoteEvent -> { + RenderLongFormContent(note, loggedIn, accountViewModel, navController) + } + + is BadgeAwardEvent -> { + RenderBadgeAward(note, backgroundColor, accountViewModel, navController) + } + + is PrivateDmEvent -> { + RenderPrivateMessage(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, navController) + } + + is HighlightEvent -> { + RenderHighlight(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, navController) + } + + is PollNoteEvent -> { + RenderPoll( + note, + makeItShort, + isAcceptableAndCanPreview.second, + backgroundColor, + accountViewModel, + navController + ) + } + + else -> { + RenderTextEvent( + note, + makeItShort, + isAcceptableAndCanPreview.second, + backgroundColor, + accountViewModel, + navController + ) + } + } + + NoteQuickActionMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel) + } } } } @@ -424,13 +443,14 @@ private fun RenderTextEvent( accountViewModel: AccountViewModel, navController: NavController ) { - val noteEvent = note.event ?: return - + val tags = remember { note.event?.tags() } + val hashtags = remember { note.event?.hashtags() ?: emptyList() } val eventContent = remember { accountViewModel.decrypt(note) } val modifier = remember { Modifier.fillMaxWidth() } + val isAuthorTheLoggedUser = remember { accountViewModel.isLoggedUser(note.author) } if (eventContent != null) { - if (makeItShort && accountViewModel.isLoggedUser(note.author)) { + if (makeItShort && isAuthorTheLoggedUser) { Text( text = eventContent, color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), @@ -442,13 +462,13 @@ private fun RenderTextEvent( content = eventContent, canPreview = canPreview && !makeItShort, modifier = modifier, - tags = noteEvent.tags(), + tags = tags, backgroundColor = backgroundColor, accountViewModel = accountViewModel, navController = navController ) - DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController) + DisplayUncitedHashtags(hashtags, eventContent, navController) } } @@ -637,9 +657,11 @@ private fun RenderBadgeAward( FlowRow(modifier = Modifier.padding(top = 5.dp)) { awardees.take(100).forEach { user -> Row( - modifier = Modifier.size(size = 35.dp).clickable { - navController.navigate("User/${user.pubkeyHex}") - }, + modifier = Modifier + .size(size = 35.dp) + .clickable { + navController.navigate("User/${user.pubkeyHex}") + }, verticalAlignment = Alignment.CenterVertically ) { UserPicture( @@ -711,7 +733,11 @@ private fun RenderRepost( accountViewModel: AccountViewModel, navController: NavController ) { - note.replyTo?.lastOrNull()?.let { + val boostedNote = remember { + note.replyTo?.lastOrNull() + } + + boostedNote?.let { NoteCompose( it, modifier = Modifier, @@ -745,9 +771,16 @@ private fun RenderLongFormContent( @Composable private fun RenderReport(note: Note) { - val noteEvent = note.event as? ReportEvent ?: return + val base = remember { + val noteEvent = note.event as? ReportEvent + if (noteEvent == null) { + emptyList() + } else { + (noteEvent.reportedPost() + noteEvent.reportedAuthor()) + } + } - val reportType = (noteEvent.reportedPost() + noteEvent.reportedAuthor()).map { + val reportType = base.map { when (it.reportType) { ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content) ReportEvent.ReportType.NUDITY -> stringResource(R.string.nudity) @@ -780,7 +813,9 @@ private fun ReplyRow( val noteEvent = note.event if (noteEvent is TextNoteEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) { - val replyingDirectlyTo = note.replyTo?.lastOrNull() + val replyingDirectlyTo = remember { + note.replyTo?.lastOrNull() + } if (replyingDirectlyTo != null && unPackReply) { NoteCompose( baseNote = replyingDirectlyTo, @@ -821,18 +856,18 @@ private fun SecondUserInfoRow( account: Account, navController: NavController ) { - val noteEvent = note.event ?: return - val noteAuthor = note.author ?: return + val noteEvent = remember { note.event } ?: return + val noteAuthor = remember { note.author } ?: return Row(verticalAlignment = Alignment.CenterVertically) { ObserveDisplayNip05Status(noteAuthor, Modifier.weight(1f)) - val baseReward = noteEvent.getReward() + val baseReward = remember { noteEvent.getReward() } if (baseReward != null) { DisplayReward(baseReward, note, account, navController) } - val pow = noteEvent.getPoWRank() + val pow = remember { noteEvent.getPoWRank() } if (pow > 20) { DisplayPoW(pow) } @@ -848,14 +883,17 @@ private fun FirstUserInfoRow( navController: NavController ) { var moreActionsExpanded by remember { mutableStateOf(false) } - val eventNote = baseNote.event ?: return - val time = baseNote.createdAt() ?: return - val loggedIn = account.userProfile() + val eventNote = remember { baseNote.event } ?: return + val time = remember { baseNote.createdAt() } ?: return + val loggedIn = remember { account.userProfile() } + val padding = remember { + Modifier.padding(horizontal = 5.dp) + } Row(verticalAlignment = Alignment.CenterVertically) { if (showAuthorPicture) { NoteAuthorPicture(baseNote, navController, loggedIn, 25.dp) - Spacer(Modifier.padding(horizontal = 5.dp)) + Spacer(padding) NoteUsernameDisplay(baseNote, Modifier.weight(1f)) } else { NoteUsernameDisplay(baseNote, Modifier.weight(1f)) @@ -916,10 +954,11 @@ fun TimeAgo(time: Long) { @Composable private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavController) { val baseChannel = remember { baseNote.channel() } + val modifier = remember { Modifier.width(55.dp) } - Column(Modifier.width(55.dp)) { + Column(modifier) { // Draws the boosted picture outside the boosted card. - Box(modifier = Modifier.width(55.dp), contentAlignment = Alignment.BottomEnd) { + Box(modifier = modifier, contentAlignment = Alignment.BottomEnd) { NoteAuthorPicture(baseNote, navController, loggedIn, 55.dp) if (baseNote.event is RepostEvent) { @@ -932,7 +971,10 @@ private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavC } if (baseNote.event is RepostEvent) { - baseNote.replyTo?.lastOrNull()?.let { + val baseReply = remember { + baseNote.replyTo?.lastOrNull() + } + baseReply?.let { RelayBadges(it) } } else { @@ -946,20 +988,30 @@ private fun ChannelNotePicture(baseChannel: Channel) { val channelState by baseChannel.live.observeAsState() val channel = channelState?.channel + val modifier = remember { + Modifier + .width(30.dp) + .height(30.dp) + .clip(shape = CircleShape) + } + + val boxModifier = remember { + Modifier + .width(30.dp) + .height(30.dp) + } + if (channel != null) { - Box( - Modifier - .width(30.dp) - .height(30.dp) - ) { + val model = remember(channelState) { + ResizeImage(channel.profilePicture(), 30.dp) + } + + Box(boxModifier) { RobohashAsyncImageProxy( robot = channel.idHex, - model = ResizeImage(channel.profilePicture(), 30.dp), + model = model, contentDescription = stringResource(R.string.group_picture), - modifier = Modifier - .width(30.dp) - .height(30.dp) - .clip(shape = CircleShape) + modifier = modifier .background(MaterialTheme.colors.background) .border( 2.dp, @@ -977,12 +1029,16 @@ private fun RepostNoteAuthorPicture( navController: NavController, loggedIn: User ) { - baseNote.replyTo?.lastOrNull()?.let { - Box( - Modifier - .width(30.dp) - .height(30.dp) - ) { + val baseRepost = remember { baseNote.replyTo?.lastOrNull() } + + val modifier = remember { + Modifier + .width(30.dp) + .height(30.dp) + } + + baseRepost?.let { + Box(modifier) { NoteAuthorPicture( it, navController, @@ -1041,13 +1097,14 @@ fun DisplayHighlight( authorHex?.let { authorHex -> userBase?.let { userBase -> val userState by userBase.live().metadata.observeAsState() - val user = userState?.user + val route = remember { "User/${userBase.pubkeyHex}" } + val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() } - if (user != null) { + if (userDisplayName != null) { CreateClickableText( - user.toBestDisplayName(), + userDisplayName, "", - "User/${user.pubkeyHex}", + route, navController ) } @@ -1055,11 +1112,13 @@ fun DisplayHighlight( } url?.let { url -> - val validatedUrl = try { - URL(url) - } catch (e: Exception) { - Log.w("Note Compose", "Invalid URI: $url") - null + val validatedUrl = remember { + try { + URL(url) + } catch (e: Exception) { + Log.w("Note Compose", "Invalid URI: $url") + null + } } validatedUrl?.host?.let { host -> @@ -1524,44 +1583,59 @@ private fun VerticalRelayPanelWithFlow( private fun RelayIconCompose(url: String) { val uri = LocalUriHandler.current - Box( + val model = remember(url) { "https://$url/favicon.ico" } + + val boxModifier = remember { Modifier .padding(1.dp) .size(15.dp) - ) { + } + val iconModifier = remember(url) { + Modifier + .width(13.dp) + .height(13.dp) + .clip(shape = CircleShape) + .clickable(onClick = { uri.openUri("https://$url") }) + } + val colorFilter = remember { + ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) }) + } + + Box(boxModifier) { RobohashFallbackAsyncImage( - robot = "https://$url/favicon.ico", + robot = model, robotSize = 15.dp, - model = "https://$url/favicon.ico", + model = model, contentDescription = stringResource(R.string.relay_icon), - colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) }), - modifier = Modifier - .width(13.dp) - .height(13.dp) - .clip(shape = CircleShape) - .background(MaterialTheme.colors.background) - .clickable(onClick = { uri.openUri("https://$url") }) + colorFilter = colorFilter, + modifier = iconModifier.background(MaterialTheme.colors.background) ) } } @Composable private fun ShowMoreRelaysButton(onClick: () -> Unit) { - Row( + val boxModifier = remember { Modifier .fillMaxWidth() - .height(25.dp), + .height(25.dp) + } + val iconButtonModifier = remember { Modifier.size(24.dp) } + val iconModifier = remember { Modifier.size(15.dp) } + + Row( + boxModifier, horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.Top ) { IconButton( - modifier = Modifier.then(Modifier.size(24.dp)), + modifier = iconButtonModifier, onClick = onClick ) { Icon( imageVector = Icons.Default.ExpandMore, null, - modifier = Modifier.size(15.dp), + modifier = iconModifier, tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) ) } @@ -1590,25 +1664,30 @@ fun NoteAuthorPicture( onClick: ((User) -> Unit)? = null ) { val noteState by baseNote.live().metadata.observeAsState() - val note = noteState?.note ?: return + val author = remember(noteState) { + noteState?.note?.author + } - val author = note.author - - Box( + val boxModifier = remember { Modifier .width(size) .height(size) - ) { + } + + val nullModifier = remember { + modifier + .width(size) + .height(size) + .clip(shape = CircleShape) + } + + Box(boxModifier) { if (author == null) { RobohashAsyncImage( robot = "authornotfound", robotSize = size, contentDescription = stringResource(R.string.unknown_author), - modifier = modifier - .width(size) - .height(size) - .clip(shape = CircleShape) - .background(MaterialTheme.colors.background) + modifier = nullModifier.background(MaterialTheme.colors.background) ) } else { UserPicture(author, baseUserAccount, size, modifier, onClick) @@ -1640,31 +1719,38 @@ fun UserPicture( onLongClick: ((User) -> Unit)? = null ) { val userState by baseUser.live().metadata.observeAsState() - val user = userState?.user ?: return - val accountState by baseUserAccount.live().follows.observeAsState() - val accountUser = accountState?.user ?: return - val showFollowingMark = accountUser.isFollowingCached(user) || user == accountUser + val userPubkey = remember { + baseUser.pubkeyHex + } - 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 - } - } - ) { + val userProfile = remember(userState) { + userState?.user?.profilePicture() + } + + val showFollowingMark = remember(accountState) { + accountState?.user?.isFollowingCached(baseUser) == true || baseUser === accountState?.user + } + + // BaseUser is the same reference as accountState.user + val myModifier = remember { + if (onClick != null && onLongClick != null) { + Modifier.combinedClickable( + onClick = { onClick(baseUser) }, + onLongClick = { onLongClick(baseUser) } + ) + } else if (onClick != null) { + Modifier.clickable(onClick = { onClick(baseUser) }) + } else { + Modifier + } + } + + Row(modifier = myModifier) { UserPicture( - userHex = user.pubkeyHex, - userPicture = user.profilePicture(), + userHex = userPubkey, + userPicture = userProfile, showFollowingMark = showFollowingMark, size = size, modifier = modifier @@ -1680,43 +1766,59 @@ fun UserPicture( size: Dp, modifier: Modifier = Modifier ) { - Box( + val myBoxModifier = remember { Modifier .width(size) .height(size) - ) { + } + + val myImageModifier = remember { + modifier + .width(size) + .height(size) + .clip(shape = CircleShape) + } + + val myResizeImage = remember(userPicture) { + ResizeImage(userPicture, size) + } + + Box(myBoxModifier) { RobohashAsyncImageProxy( robot = userHex, - model = ResizeImage(userPicture, size), + model = myResizeImage, contentDescription = stringResource(id = R.string.profile_image), - modifier = modifier - .width(size) - .height(size) - .clip(shape = CircleShape) - .background(MaterialTheme.colors.background) + modifier = myImageModifier.background(MaterialTheme.colors.background) ) if (showFollowingMark) { - Box( + val myIconBoxModifier = remember { Modifier .width(size.div(3.5f)) .height(size.div(3.5f)) - .align(Alignment.TopEnd), - contentAlignment = Alignment.Center - ) { - // Background for the transparent checkmark + .align(Alignment.TopEnd) + } + + val myIconBackgroundModifier = remember { + Modifier + .clip(CircleShape) + .fillMaxSize(0.6f) + .align(Alignment.Center) + } + + val myIconModifier = remember { + Modifier.fillMaxSize() + } + + Box(myIconBoxModifier, contentAlignment = Alignment.Center) { Box( - Modifier - .clip(CircleShape) - .fillMaxSize(0.6f) - .align(Alignment.Center) - .background(MaterialTheme.colors.background) + myIconBackgroundModifier.background(MaterialTheme.colors.background) ) Icon( painter = painterResource(R.drawable.ic_verified), stringResource(id = R.string.following), - modifier = Modifier.fillMaxSize(), + modifier = myIconModifier, tint = Following ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index 77cf78c93..36891d3b5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -68,7 +68,7 @@ import kotlin.math.roundToInt @Composable fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel, navController: NavController) { val accountState by accountViewModel.accountLiveData.observeAsState() - val account = accountState?.account ?: return + val account = remember(accountState) { accountState?.account } ?: return val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) @@ -123,13 +123,21 @@ fun ReplyReaction( onPress: () -> Unit ) { val repliesState by baseNote.live().replies.observeAsState() - val replies = repliesState?.note?.replies ?: emptySet() + val replies = remember(repliesState) { repliesState?.note?.replies } ?: emptySet() val context = LocalContext.current val scope = rememberCoroutineScope() + val iconButtonModifier = remember { + Modifier.size(iconSize) + } + + val iconModifier = remember { + Modifier.size(iconSize) + } + IconButton( - modifier = Modifier.size(iconSize), + modifier = iconButtonModifier, onClick = { if (accountViewModel.isWriteable()) { onPress() @@ -147,7 +155,7 @@ fun ReplyReaction( Icon( painter = painterResource(R.drawable.ic_comment), null, - modifier = Modifier.size(iconSize), + modifier = iconModifier, tint = grayTint ) } @@ -162,7 +170,7 @@ fun ReplyReaction( } @Composable -public fun BoostReaction( +fun BoostReaction( baseNote: Note, grayTint: Color, accountViewModel: AccountViewModel, @@ -170,17 +178,32 @@ public fun BoostReaction( onQuotePress: () -> Unit ) { val boostsState by baseNote.live().boosts.observeAsState() - val boostedNote = boostsState?.note + val boostedNote = remember(boostsState) { boostsState?.note } ?: return + + val hasBoosted = remember(boostsState) { accountViewModel.hasBoosted(baseNote) } + val wasBoostedByLoggedIn = remember(boostsState) { boostedNote.isBoostedBy(accountViewModel.userProfile()) } + val isWriteable = remember { accountViewModel.isWriteable() } + + val boostCount = remember(boostsState) { showCount(boostedNote.boosts.size) } + val context = LocalContext.current val scope = rememberCoroutineScope() var wantsToBoost by remember { mutableStateOf(false) } + val iconButtonModifier = remember { + Modifier.size(iconSize) + } + + val iconModifier = remember { + Modifier.size(iconSize) + } + IconButton( - modifier = Modifier.then(Modifier.size(iconSize)), + modifier = iconButtonModifier, onClick = { - if (accountViewModel.isWriteable()) { - if (accountViewModel.hasBoosted(baseNote)) { + if (isWriteable) { + if (hasBoosted) { accountViewModel.deleteBoostsTo(baseNote) } else { wantsToBoost = true @@ -210,25 +233,16 @@ public fun BoostReaction( ) } - if (boostedNote?.isBoostedBy(accountViewModel.userProfile()) == true) { - Icon( - painter = painterResource(R.drawable.ic_retweeted), - null, - modifier = Modifier.size(iconSize), - tint = Color.Unspecified - ) - } else { - Icon( - painter = painterResource(R.drawable.ic_retweet), - null, - modifier = Modifier.size(iconSize), - tint = grayTint - ) - } + Icon( + painter = painterResource(R.drawable.ic_retweeted), + null, + modifier = iconModifier, + tint = if (wasBoostedByLoggedIn) Color.Unspecified else grayTint + ) } Text( - " ${showCount(boostedNote?.boosts?.size)}", + " $boostCount", fontSize = 14.sp, color = grayTint ) @@ -243,16 +257,30 @@ fun LikeReaction( heartSize: Dp = 16.dp ) { val reactionsState by baseNote.live().reactions.observeAsState() - val reactedNote = reactionsState?.note ?: return + val reactedNote = remember(reactionsState) { reactionsState?.note } ?: return + + val hasReacted = remember(reactionsState) { accountViewModel.hasReactedTo(baseNote) } + val wasReactedByLoggedIn = remember(reactionsState) { reactedNote.isReactedBy(accountViewModel.userProfile()) } + val isWriteable = remember { accountViewModel.isWriteable() } + + val reactionCount = remember(reactionsState) { showCount(reactedNote.reactions.size) } val context = LocalContext.current val scope = rememberCoroutineScope() + val iconButtonModifier = remember { + Modifier.size(iconSize) + } + + val iconModifier = remember { + Modifier.size(heartSize) + } + IconButton( - modifier = Modifier.then(Modifier.size(iconSize)), + modifier = iconButtonModifier, onClick = { - if (accountViewModel.isWriteable()) { - if (accountViewModel.hasReactedTo(baseNote)) { + if (isWriteable) { + if (hasReacted) { accountViewModel.deleteReactionTo(baseNote) } else { accountViewModel.reactTo(baseNote) @@ -268,25 +296,25 @@ fun LikeReaction( } } ) { - if (reactedNote.isReactedBy(accountViewModel.userProfile())) { + if (wasReactedByLoggedIn) { Icon( painter = painterResource(R.drawable.ic_liked), null, - modifier = Modifier.size(heartSize), + modifier = iconModifier, tint = Color.Unspecified ) } else { Icon( painter = painterResource(R.drawable.ic_like), null, - modifier = Modifier.size(heartSize), + modifier = iconModifier, tint = grayTint ) } } Text( - " ${showCount(reactedNote.reactions.size)}", + " $reactionCount", fontSize = 14.sp, color = grayTint ) @@ -472,6 +500,19 @@ public fun ViewCountReaction( numberSize: Dp = 24.dp ) { val uri = LocalUriHandler.current + val context = LocalContext.current + + val iconButtonModifier = remember { + Modifier.size(barChartSize) + } + + val iconModifier = remember { + Modifier.height(numberSize) + } + + val colorFilter = remember { + ColorFilter.tint(grayTint) + } IconButton( modifier = Modifier.size(iconSize), @@ -480,21 +521,25 @@ public fun ViewCountReaction( Icon( imageVector = Icons.Outlined.BarChart, null, - modifier = Modifier.size(barChartSize), + modifier = iconButtonModifier, tint = grayTint ) } - Row() { + val request = remember { + ImageRequest.Builder(context) + .data("https://counter.amethyst.social/$idHex.svg?label=+&color=00000000") + .diskCachePolicy(CachePolicy.DISABLED) + .memoryCachePolicy(CachePolicy.ENABLED) + .build() + } + + Row { AsyncImage( - model = ImageRequest.Builder(LocalContext.current) - .data("https://counter.amethyst.social/$idHex.svg?label=+&color=00000000") - .diskCachePolicy(CachePolicy.DISABLED) - .memoryCachePolicy(CachePolicy.ENABLED) - .build(), + model = request, contentDescription = stringResource(R.string.view_count), - modifier = Modifier.height(numberSize), - colorFilter = ColorFilter.tint(grayTint) + modifier = iconModifier, + colorFilter = colorFilter ) } }