Trying to improve rendering by allowing recompositions to perform layout faster.

This commit is contained in:
Vitor Pamplona 2023-05-10 19:08:56 -04:00
parent 8fcd05a6bb
commit b1de36f423
5 changed files with 720 additions and 502 deletions

View File

@ -1,6 +1,7 @@
package com.vitorpamplona.amethyst.ui.components package com.vitorpamplona.amethyst.ui.components
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
@ -30,13 +31,21 @@ fun RobohashAsyncImage(
colorFilter: ColorFilter? = null, colorFilter: ColorFilter? = null,
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality filterQuality: FilterQuality = DrawScope.DefaultFilterQuality
) { ) {
with(LocalDensity.current) { val context = LocalContext.current
AsyncImage( val size = with(LocalDensity.current) {
model = Robohash.imageRequest( robotSize.roundToPx()
LocalContext.current, }
val imageRequest = remember(size, robot) {
Robohash.imageRequest(
context,
robot, robot,
Size(robotSize.roundToPx(), robotSize.roundToPx()) Size(size, size)
), )
}
AsyncImage(
model = imageRequest,
contentDescription = contentDescription, contentDescription = contentDescription,
modifier = modifier, modifier = modifier,
transform = transform, transform = transform,
@ -47,7 +56,6 @@ fun RobohashAsyncImage(
colorFilter = colorFilter, colorFilter = colorFilter,
filterQuality = filterQuality filterQuality = filterQuality
) )
}
} }
var imageErrors = setOf<String>() var imageErrors = setOf<String>()
@ -120,7 +128,7 @@ fun RobohashAsyncImageProxy(
colorFilter: ColorFilter? = null, colorFilter: ColorFilter? = null,
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality filterQuality: FilterQuality = DrawScope.DefaultFilterQuality
) { ) {
val proxy = model.proxyUrl() val proxy = remember(model) { model.proxyUrl() }
if (proxy == null) { if (proxy == null) {
RobohashAsyncImage( RobohashAsyncImage(
robot = robot, robot = robot,

View File

@ -6,6 +6,7 @@ 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
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
@ -34,18 +35,18 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @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 noteState by messageSetCard.note.live().metadata.observeAsState()
val note = noteState?.note val note = remember(noteState) { noteState?.note }
var popupExpanded by remember { mutableStateOf(false) } var popupExpanded by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
if (note == null) { if (note == null) {
BlankNote(Modifier, isInnerNote) BlankNote(Modifier)
} else { } else {
var isNew by remember { mutableStateOf<Boolean>(false) } var isNew by remember { mutableStateOf(false) }
LaunchedEffect(key1 = messageSetCard.createdAt()) { LaunchedEffect(key1 = messageSetCard.createdAt()) {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
@ -66,8 +67,15 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
MaterialTheme.colors.background MaterialTheme.colors.background
} }
Column( val columnModifier = remember {
modifier = Modifier.background(backgroundColor).combinedClickable( Modifier
.background(backgroundColor)
.padding(
start = 12.dp,
end = 12.dp,
top = 10.dp
)
.combinedClickable(
onClick = { onClick = {
scope.launch { scope.launch {
routeFor( routeFor(
@ -78,32 +86,21 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
}, },
onLongClick = { popupExpanded = true } onLongClick = { popupExpanded = true }
) )
) { .fillMaxWidth()
Row( }
modifier = Modifier
.padding( Column(columnModifier) {
start = if (!isInnerNote) 12.dp else 0.dp, Row(Modifier.fillMaxWidth()) {
end = if (!isInnerNote) 12.dp else 0.dp, Box(modifier = remember { Modifier.width(55.dp).padding(top = 5.dp, end = 5.dp) }) {
top = 10.dp
)
) {
// Draws the like picture outside the boosted card.
if (!isInnerNote) {
Box(
modifier = Modifier
.width(55.dp)
.padding(top = 5.dp)
) {
Icon( Icon(
painter = painterResource(R.drawable.ic_dm), painter = painterResource(R.drawable.ic_dm),
null, null,
modifier = Modifier.size(16.dp).align(Alignment.TopEnd), modifier = remember { Modifier.size(16.dp).align(Alignment.TopEnd) },
tint = MaterialTheme.colors.primary 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( NoteCompose(
baseNote = messageSetCard.note, baseNote = messageSetCard.note,
routeForLastRead = null, routeForLastRead = null,

View File

@ -19,6 +19,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bolt import androidx.compose.material.icons.filled.Bolt
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf 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.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.UserState
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.screen.MultiSetCard import com.vitorpamplona.amethyst.ui.screen.MultiSetCard
@ -52,19 +54,19 @@ import kotlinx.coroutines.launch
@Composable @Composable
fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
val noteState by multiSetCard.note.live().metadata.observeAsState() 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 accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = remember(accountState) { accountState?.account }
var popupExpanded by remember { mutableStateOf(false) } var popupExpanded by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
if (note == null) { if (note == null || account == null) {
BlankNote(Modifier, false) BlankNote(Modifier, false)
} else { } else {
var isNew by remember { mutableStateOf<Boolean>(false) } var isNew by remember { mutableStateOf(false) }
LaunchedEffect(key1 = multiSetCard.createdAt()) { LaunchedEffect(key1 = multiSetCard.createdAt()) {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
@ -84,89 +86,43 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
MaterialTheme.colors.background MaterialTheme.colors.background
} }
Column( val columnModifier = remember {
modifier = Modifier Modifier
.background(backgroundColor) .background(backgroundColor)
.combinedClickable(
onClick = {
scope.launch {
routeFor(note, account.userProfile())?.let { navController.navigate(it) }
}
},
onLongClick = { popupExpanded = true }
)
) {
Row(
modifier = Modifier
.padding( .padding(
start = 12.dp, start = 12.dp,
end = 12.dp, end = 12.dp,
top = 10.dp top = 10.dp
) )
) { .combinedClickable(
Column(Modifier.fillMaxWidth()) { onClick = {
if (multiSetCard.zapEvents.isNotEmpty()) { scope.launch {
Row(Modifier.fillMaxWidth()) { routeFor(
// Draws the like picture outside the boosted card. note,
Box( account.userProfile()
modifier = Modifier )?.let { navController.navigate(it) }
.width(55.dp) }
.padding(0.dp) },
) { onLongClick = { popupExpanded = true }
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 }
Column(modifier = columnModifier) {
if (zapEvents.isNotEmpty()) {
RenderZapGallery(zapEvents, navController, account, accountViewModel)
} }
if (multiSetCard.boostEvents.isNotEmpty()) { if (boostEvents.isNotEmpty()) {
Row(Modifier.fillMaxWidth()) { RenderBoostGallery(boostEvents, navController, account, accountViewModel)
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
)
} }
AuthorGallery(multiSetCard.boostEvents, navController, account, accountViewModel) if (likeEvents.isNotEmpty()) {
} RenderLikeGallery(likeEvents, 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
)
}
AuthorGallery(multiSetCard.likeEvents, navController, account, accountViewModel)
}
} }
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
@ -186,7 +142,98 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
} }
} }
} }
}
@Composable
private fun RenderLikeGallery(
likeEvents: List<Note>,
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<Note, Note>,
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<Note>,
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)
} }
} }
@ -197,13 +244,16 @@ fun AuthorGalleryZaps(
account: Account, account: Account,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
val accountState by account.userProfile().live().follows.observeAsState() val accountState = account.userProfile().live().follows.observeAsState()
val accountUser = accountState?.user ?: return
val listToRender = remember {
authorNotes.keys.take(50)
}
Column(modifier = Modifier.padding(start = 10.dp)) { Column(modifier = Modifier.padding(start = 10.dp)) {
FlowRow() { FlowRow() {
authorNotes.forEach { listToRender.forEach {
AuthorPictureAndComment(it.key, navController, accountUser, accountViewModel) AuthorPictureAndComment(it, navController, accountState, accountViewModel)
} }
} }
} }
@ -213,7 +263,7 @@ fun AuthorGalleryZaps(
private fun AuthorPictureAndComment( private fun AuthorPictureAndComment(
zapRequest: Note, zapRequest: Note,
navController: NavController, navController: NavController,
accountUser: User, accountUser: State<UserState?>,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
val author = zapRequest.author ?: return val author = zapRequest.author ?: return
@ -245,19 +295,25 @@ private fun AuthorPictureAndComment(
author: User, author: User,
comment: String?, comment: String?,
navController: NavController, navController: NavController,
accountUser: User, accountUser: State<UserState?>,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
val modifier = if (!comment.isNullOrBlank()) { val modifier = remember(comment) {
Modifier.fillMaxWidth() if (!comment.isNullOrBlank()) {
} else {
Modifier Modifier
.fillMaxWidth()
.clickable {
navController.navigate("User/${author.pubkeyHex}")
}
} else {
Modifier.clickable {
navController.navigate("User/${author.pubkeyHex}")
}
}
} }
Row( Row(
modifier = modifier.clickable { modifier = modifier,
navController.navigate("User/${author.pubkeyHex}")
},
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
FastNoteAuthorPicture( FastNoteAuthorPicture(
@ -288,20 +344,22 @@ fun AuthorGallery(
account: Account, account: Account,
accountViewModel: AccountViewModel accountViewModel: AccountViewModel
) { ) {
val accountState by account.userProfile().live().follows.observeAsState() val accountState = account.userProfile().live().follows.observeAsState()
val accountUser = accountState?.user ?: return val listToRender = remember {
Pair(
authorNotes.take(50).mapNotNull { it.author },
authorNotes.size
)
}
Column(modifier = Modifier.padding(start = 10.dp)) { Column(modifier = Modifier.padding(start = 10.dp)) {
FlowRow() { FlowRow() {
authorNotes.take(50).forEach { listToRender.first.forEach { author ->
val author = it.author AuthorPictureAndComment(author, null, navController, accountState, accountViewModel)
if (author != null) {
AuthorPictureAndComment(author, null, navController, accountUser, accountViewModel)
}
} }
if (authorNotes.size > 50) { if (listToRender.second > 50) {
Text(" and ${authorNotes.size - 50} others") Text(" and ${listToRender.second - 50} others")
} }
} }
} }
@ -310,18 +368,26 @@ fun AuthorGallery(
@Composable @Composable
fun FastNoteAuthorPicture( fun FastNoteAuthorPicture(
author: User, author: User,
userAccount: User, userAccount: State<UserState?>,
size: Dp, size: Dp,
pictureModifier: Modifier = Modifier pictureModifier: Modifier = Modifier
) { ) {
val userState by author.live().metadata.observeAsState() 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( UserPicture(
userHex = user.pubkeyHex, userHex = authorPubKey,
userPicture = user.profilePicture(), userPicture = profilePicture,
showFollowingMark = showFollowingMark, showFollowingMark = showFollowingMark,
size = size, size = size,
modifier = pictureModifier modifier = pictureModifier

View File

@ -96,6 +96,7 @@ import com.vitorpamplona.amethyst.service.model.PollNoteEvent
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.ReactionEvent
import com.vitorpamplona.amethyst.service.model.ReportEvent 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.RepostEvent
import com.vitorpamplona.amethyst.service.model.TextNoteEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent
import com.vitorpamplona.amethyst.ui.components.ClickableUrl import com.vitorpamplona.amethyst.ui.components.ClickableUrl
@ -178,25 +179,44 @@ fun NoteComposeInner(
navController: NavController navController: NavController
) { ) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = remember(accountState) { accountState?.account } ?: return
val loggedIn = account.userProfile() val loggedIn = remember(accountState) { accountState?.account?.userProfile() } ?: return
val noteState by baseNote.live().metadata.observeAsState() 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 noteReportsState by baseNote.live().reports.observeAsState()
val noteForReports = noteReportsState?.note ?: return val noteForReports = remember(noteReportsState) { noteReportsState?.note } ?: return
val noteEvent = note?.event
val baseChannel = note?.channel()
var popupExpanded by remember { mutableStateOf(false) } var popupExpanded by remember { mutableStateOf(false) }
var showHiddenNote by remember { mutableStateOf(false) }
if (noteEvent == null) {
BlankNote(
remember {
modifier.combinedClickable(
onClick = { },
onLongClick = { popupExpanded = true }
)
},
isBoostedNote
)
note?.let {
NoteQuickActionMenu(it, popupExpanded, { popupExpanded = false }, accountViewModel)
}
} else {
var showHiddenNote by remember { mutableStateOf(false) }
var isAcceptableAndCanPreview by remember { mutableStateOf(Pair(true, true)) } var isAcceptableAndCanPreview by remember { mutableStateOf(Pair(true, true)) }
LaunchedEffect(key1 = noteReportsState) { LaunchedEffect(key1 = noteReportsState, key2 = accountState) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val newCanPreview = note?.author === loggedIn || account.userProfile().let { loggedIn ->
(note?.author?.let { loggedIn.isFollowingCached(it) } ?: true) || val newCanPreview = note.author === loggedIn ||
!noteForReports.hasAnyReports() (note.author?.let { loggedIn.isFollowingCached(it) } ?: true) ||
!(noteForReports.hasAnyReports())
val newIsAcceptable = account.isAcceptable(noteForReports) val newIsAcceptable = account.isAcceptable(noteForReports)
@ -205,23 +225,13 @@ fun NoteComposeInner(
} }
} }
} }
}
val noteEvent = note?.event if (!isAcceptableAndCanPreview.first && !showHiddenNote) {
val baseChannel = note?.channel()
if (noteEvent == null) {
BlankNote(
modifier.combinedClickable(
onClick = { },
onLongClick = { popupExpanded = true }
),
isBoostedNote
)
} else if (!isAcceptableAndCanPreview.first && !showHiddenNote) {
if (!account.isHidden(noteForReports.author!!)) { if (!account.isHidden(noteForReports.author!!)) {
HiddenNote( HiddenNote(
account.getRelevantReports(noteForReports), account.getRelevantReports(noteForReports),
loggedIn, account.userProfile(),
modifier, modifier,
isBoostedNote, isBoostedNote,
navController, navController,
@ -270,33 +280,41 @@ fun NoteComposeInner(
parentBackgroundColor ?: MaterialTheme.colors.background parentBackgroundColor ?: MaterialTheme.colors.background
} }
Column( val columnModifier = remember(backgroundColor) {
modifier = modifier modifier
.combinedClickable( .combinedClickable(
onClick = { onClick = {
scope.launch { scope.launch {
routeFor(note, loggedIn)?.let { navController.navigate(it) } routeFor(note, loggedIn)?.let {
navController.navigate(it)
}
} }
}, },
onLongClick = { popupExpanded = true } onLongClick = { popupExpanded = true }
) )
.background(backgroundColor) .background(backgroundColor)
) { }
Column(modifier = columnModifier) {
Row( Row(
modifier = Modifier modifier = remember {
Modifier
.padding( .padding(
start = if (!isBoostedNote) 12.dp else 0.dp, start = if (!isBoostedNote) 12.dp else 0.dp,
end = if (!isBoostedNote) 12.dp else 0.dp, end = if (!isBoostedNote) 12.dp else 0.dp,
top = if (addMarginTop && !isBoostedNote) 10.dp else 0.dp top = if (addMarginTop && !isBoostedNote) 10.dp else 0.dp
) )
}
) { ) {
if (!isBoostedNote && !isQuotedNote) { if (!isBoostedNote && !isQuotedNote) {
DrawAuthorImages(baseNote, loggedIn, navController) DrawAuthorImages(baseNote, loggedIn, navController)
} }
Column( Column(
modifier = Modifier modifier = remember {
Modifier
.padding(start = if (!isBoostedNote && !isQuotedNote) 10.dp else 0.dp) .padding(start = if (!isBoostedNote && !isQuotedNote) 10.dp else 0.dp)
}
) { ) {
FirstUserInfoRow( FirstUserInfoRow(
baseNote = baseNote, baseNote = baseNote,
@ -384,6 +402,7 @@ fun NoteComposeInner(
} }
} }
} }
}
} }
fun routeFor(note: Note, loggedIn: User): String? { fun routeFor(note: Note, loggedIn: User): String? {
@ -424,13 +443,14 @@ private fun RenderTextEvent(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController 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 eventContent = remember { accountViewModel.decrypt(note) }
val modifier = remember { Modifier.fillMaxWidth() } val modifier = remember { Modifier.fillMaxWidth() }
val isAuthorTheLoggedUser = remember { accountViewModel.isLoggedUser(note.author) }
if (eventContent != null) { if (eventContent != null) {
if (makeItShort && accountViewModel.isLoggedUser(note.author)) { if (makeItShort && isAuthorTheLoggedUser) {
Text( Text(
text = eventContent, text = eventContent,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
@ -442,13 +462,13 @@ private fun RenderTextEvent(
content = eventContent, content = eventContent,
canPreview = canPreview && !makeItShort, canPreview = canPreview && !makeItShort,
modifier = modifier, modifier = modifier,
tags = noteEvent.tags(), tags = tags,
backgroundColor = backgroundColor, backgroundColor = backgroundColor,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController navController = navController
) )
DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController) DisplayUncitedHashtags(hashtags, eventContent, navController)
} }
} }
@ -637,7 +657,9 @@ private fun RenderBadgeAward(
FlowRow(modifier = Modifier.padding(top = 5.dp)) { FlowRow(modifier = Modifier.padding(top = 5.dp)) {
awardees.take(100).forEach { user -> awardees.take(100).forEach { user ->
Row( Row(
modifier = Modifier.size(size = 35.dp).clickable { modifier = Modifier
.size(size = 35.dp)
.clickable {
navController.navigate("User/${user.pubkeyHex}") navController.navigate("User/${user.pubkeyHex}")
}, },
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@ -711,7 +733,11 @@ private fun RenderRepost(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController navController: NavController
) { ) {
note.replyTo?.lastOrNull()?.let { val boostedNote = remember {
note.replyTo?.lastOrNull()
}
boostedNote?.let {
NoteCompose( NoteCompose(
it, it,
modifier = Modifier, modifier = Modifier,
@ -745,9 +771,16 @@ private fun RenderLongFormContent(
@Composable @Composable
private fun RenderReport(note: Note) { 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<ReportedKey>()
} else {
(noteEvent.reportedPost() + noteEvent.reportedAuthor())
}
}
val reportType = (noteEvent.reportedPost() + noteEvent.reportedAuthor()).map { val reportType = base.map {
when (it.reportType) { when (it.reportType) {
ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content) ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content)
ReportEvent.ReportType.NUDITY -> stringResource(R.string.nudity) ReportEvent.ReportType.NUDITY -> stringResource(R.string.nudity)
@ -780,7 +813,9 @@ private fun ReplyRow(
val noteEvent = note.event val noteEvent = note.event
if (noteEvent is TextNoteEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) { if (noteEvent is TextNoteEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) {
val replyingDirectlyTo = note.replyTo?.lastOrNull() val replyingDirectlyTo = remember {
note.replyTo?.lastOrNull()
}
if (replyingDirectlyTo != null && unPackReply) { if (replyingDirectlyTo != null && unPackReply) {
NoteCompose( NoteCompose(
baseNote = replyingDirectlyTo, baseNote = replyingDirectlyTo,
@ -821,18 +856,18 @@ private fun SecondUserInfoRow(
account: Account, account: Account,
navController: NavController navController: NavController
) { ) {
val noteEvent = note.event ?: return val noteEvent = remember { note.event } ?: return
val noteAuthor = note.author ?: return val noteAuthor = remember { note.author } ?: return
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
ObserveDisplayNip05Status(noteAuthor, Modifier.weight(1f)) ObserveDisplayNip05Status(noteAuthor, Modifier.weight(1f))
val baseReward = noteEvent.getReward() val baseReward = remember { noteEvent.getReward() }
if (baseReward != null) { if (baseReward != null) {
DisplayReward(baseReward, note, account, navController) DisplayReward(baseReward, note, account, navController)
} }
val pow = noteEvent.getPoWRank() val pow = remember { noteEvent.getPoWRank() }
if (pow > 20) { if (pow > 20) {
DisplayPoW(pow) DisplayPoW(pow)
} }
@ -848,14 +883,17 @@ private fun FirstUserInfoRow(
navController: NavController navController: NavController
) { ) {
var moreActionsExpanded by remember { mutableStateOf(false) } var moreActionsExpanded by remember { mutableStateOf(false) }
val eventNote = baseNote.event ?: return val eventNote = remember { baseNote.event } ?: return
val time = baseNote.createdAt() ?: return val time = remember { baseNote.createdAt() } ?: return
val loggedIn = account.userProfile() val loggedIn = remember { account.userProfile() }
val padding = remember {
Modifier.padding(horizontal = 5.dp)
}
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
if (showAuthorPicture) { if (showAuthorPicture) {
NoteAuthorPicture(baseNote, navController, loggedIn, 25.dp) NoteAuthorPicture(baseNote, navController, loggedIn, 25.dp)
Spacer(Modifier.padding(horizontal = 5.dp)) Spacer(padding)
NoteUsernameDisplay(baseNote, Modifier.weight(1f)) NoteUsernameDisplay(baseNote, Modifier.weight(1f))
} else { } else {
NoteUsernameDisplay(baseNote, Modifier.weight(1f)) NoteUsernameDisplay(baseNote, Modifier.weight(1f))
@ -916,10 +954,11 @@ fun TimeAgo(time: Long) {
@Composable @Composable
private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavController) { private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavController) {
val baseChannel = remember { baseNote.channel() } 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. // 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) NoteAuthorPicture(baseNote, navController, loggedIn, 55.dp)
if (baseNote.event is RepostEvent) { if (baseNote.event is RepostEvent) {
@ -932,7 +971,10 @@ private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavC
} }
if (baseNote.event is RepostEvent) { if (baseNote.event is RepostEvent) {
baseNote.replyTo?.lastOrNull()?.let { val baseReply = remember {
baseNote.replyTo?.lastOrNull()
}
baseReply?.let {
RelayBadges(it) RelayBadges(it)
} }
} else { } else {
@ -946,20 +988,30 @@ private fun ChannelNotePicture(baseChannel: Channel) {
val channelState by baseChannel.live.observeAsState() val channelState by baseChannel.live.observeAsState()
val channel = channelState?.channel val channel = channelState?.channel
if (channel != null) { val modifier = remember {
Box(
Modifier Modifier
.width(30.dp) .width(30.dp)
.height(30.dp) .height(30.dp)
) { .clip(shape = CircleShape)
RobohashAsyncImageProxy( }
robot = channel.idHex,
model = ResizeImage(channel.profilePicture(), 30.dp), val boxModifier = remember {
contentDescription = stringResource(R.string.group_picture), Modifier
modifier = Modifier
.width(30.dp) .width(30.dp)
.height(30.dp) .height(30.dp)
.clip(shape = CircleShape) }
if (channel != null) {
val model = remember(channelState) {
ResizeImage(channel.profilePicture(), 30.dp)
}
Box(boxModifier) {
RobohashAsyncImageProxy(
robot = channel.idHex,
model = model,
contentDescription = stringResource(R.string.group_picture),
modifier = modifier
.background(MaterialTheme.colors.background) .background(MaterialTheme.colors.background)
.border( .border(
2.dp, 2.dp,
@ -977,12 +1029,16 @@ private fun RepostNoteAuthorPicture(
navController: NavController, navController: NavController,
loggedIn: User loggedIn: User
) { ) {
baseNote.replyTo?.lastOrNull()?.let { val baseRepost = remember { baseNote.replyTo?.lastOrNull() }
Box(
val modifier = remember {
Modifier Modifier
.width(30.dp) .width(30.dp)
.height(30.dp) .height(30.dp)
) { }
baseRepost?.let {
Box(modifier) {
NoteAuthorPicture( NoteAuthorPicture(
it, it,
navController, navController,
@ -1041,13 +1097,14 @@ fun DisplayHighlight(
authorHex?.let { authorHex -> authorHex?.let { authorHex ->
userBase?.let { userBase -> userBase?.let { userBase ->
val userState by userBase.live().metadata.observeAsState() 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( CreateClickableText(
user.toBestDisplayName(), userDisplayName,
"", "",
"User/${user.pubkeyHex}", route,
navController navController
) )
} }
@ -1055,12 +1112,14 @@ fun DisplayHighlight(
} }
url?.let { url -> url?.let { url ->
val validatedUrl = try { val validatedUrl = remember {
try {
URL(url) URL(url)
} catch (e: Exception) { } catch (e: Exception) {
Log.w("Note Compose", "Invalid URI: $url") Log.w("Note Compose", "Invalid URI: $url")
null null
} }
}
validatedUrl?.host?.let { host -> validatedUrl?.host?.let { host ->
Text("on ") Text("on ")
@ -1524,44 +1583,59 @@ private fun VerticalRelayPanelWithFlow(
private fun RelayIconCompose(url: String) { private fun RelayIconCompose(url: String) {
val uri = LocalUriHandler.current val uri = LocalUriHandler.current
Box( val model = remember(url) { "https://$url/favicon.ico" }
val boxModifier = remember {
Modifier Modifier
.padding(1.dp) .padding(1.dp)
.size(15.dp) .size(15.dp)
) { }
RobohashFallbackAsyncImage( val iconModifier = remember(url) {
robot = "https://$url/favicon.ico", Modifier
robotSize = 15.dp,
model = "https://$url/favicon.ico",
contentDescription = stringResource(R.string.relay_icon),
colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) }),
modifier = Modifier
.width(13.dp) .width(13.dp)
.height(13.dp) .height(13.dp)
.clip(shape = CircleShape) .clip(shape = CircleShape)
.background(MaterialTheme.colors.background)
.clickable(onClick = { uri.openUri("https://$url") }) .clickable(onClick = { uri.openUri("https://$url") })
}
val colorFilter = remember {
ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) })
}
Box(boxModifier) {
RobohashFallbackAsyncImage(
robot = model,
robotSize = 15.dp,
model = model,
contentDescription = stringResource(R.string.relay_icon),
colorFilter = colorFilter,
modifier = iconModifier.background(MaterialTheme.colors.background)
) )
} }
} }
@Composable @Composable
private fun ShowMoreRelaysButton(onClick: () -> Unit) { private fun ShowMoreRelaysButton(onClick: () -> Unit) {
Row( val boxModifier = remember {
Modifier Modifier
.fillMaxWidth() .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, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.Top verticalAlignment = Alignment.Top
) { ) {
IconButton( IconButton(
modifier = Modifier.then(Modifier.size(24.dp)), modifier = iconButtonModifier,
onClick = onClick onClick = onClick
) { ) {
Icon( Icon(
imageVector = Icons.Default.ExpandMore, imageVector = Icons.Default.ExpandMore,
null, null,
modifier = Modifier.size(15.dp), modifier = iconModifier,
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
} }
@ -1590,25 +1664,30 @@ fun NoteAuthorPicture(
onClick: ((User) -> Unit)? = null onClick: ((User) -> Unit)? = null
) { ) {
val noteState by baseNote.live().metadata.observeAsState() val noteState by baseNote.live().metadata.observeAsState()
val note = noteState?.note ?: return val author = remember(noteState) {
noteState?.note?.author
}
val author = note.author val boxModifier = remember {
Box(
Modifier Modifier
.width(size) .width(size)
.height(size) .height(size)
) { }
val nullModifier = remember {
modifier
.width(size)
.height(size)
.clip(shape = CircleShape)
}
Box(boxModifier) {
if (author == null) { if (author == null) {
RobohashAsyncImage( RobohashAsyncImage(
robot = "authornotfound", robot = "authornotfound",
robotSize = size, robotSize = size,
contentDescription = stringResource(R.string.unknown_author), contentDescription = stringResource(R.string.unknown_author),
modifier = modifier modifier = nullModifier.background(MaterialTheme.colors.background)
.width(size)
.height(size)
.clip(shape = CircleShape)
.background(MaterialTheme.colors.background)
) )
} else { } else {
UserPicture(author, baseUserAccount, size, modifier, onClick) UserPicture(author, baseUserAccount, size, modifier, onClick)
@ -1640,31 +1719,38 @@ fun UserPicture(
onLongClick: ((User) -> Unit)? = null onLongClick: ((User) -> Unit)? = null
) { ) {
val userState by baseUser.live().metadata.observeAsState() val userState by baseUser.live().metadata.observeAsState()
val user = userState?.user ?: return
val accountState by baseUserAccount.live().follows.observeAsState() 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( val userProfile = remember(userState) {
modifier = Modifier userState?.user?.profilePicture()
.run { }
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) { if (onClick != null && onLongClick != null) {
this.combinedClickable( Modifier.combinedClickable(
onClick = { onClick(user) }, onClick = { onClick(baseUser) },
onLongClick = { onLongClick(user) } onLongClick = { onLongClick(baseUser) }
) )
} else if (onClick != null) { } else if (onClick != null) {
this.clickable(onClick = { onClick(user) }) Modifier.clickable(onClick = { onClick(baseUser) })
} else { } else {
this Modifier
} }
} }
) {
Row(modifier = myModifier) {
UserPicture( UserPicture(
userHex = user.pubkeyHex, userHex = userPubkey,
userPicture = user.profilePicture(), userPicture = userProfile,
showFollowingMark = showFollowingMark, showFollowingMark = showFollowingMark,
size = size, size = size,
modifier = modifier modifier = modifier
@ -1680,43 +1766,59 @@ fun UserPicture(
size: Dp, size: Dp,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Box( val myBoxModifier = remember {
Modifier Modifier
.width(size) .width(size)
.height(size) .height(size)
) { }
RobohashAsyncImageProxy(
robot = userHex, val myImageModifier = remember {
model = ResizeImage(userPicture, size), modifier
contentDescription = stringResource(id = R.string.profile_image),
modifier = modifier
.width(size) .width(size)
.height(size) .height(size)
.clip(shape = CircleShape) .clip(shape = CircleShape)
.background(MaterialTheme.colors.background) }
val myResizeImage = remember(userPicture) {
ResizeImage(userPicture, size)
}
Box(myBoxModifier) {
RobohashAsyncImageProxy(
robot = userHex,
model = myResizeImage,
contentDescription = stringResource(id = R.string.profile_image),
modifier = myImageModifier.background(MaterialTheme.colors.background)
) )
if (showFollowingMark) { if (showFollowingMark) {
Box( val myIconBoxModifier = remember {
Modifier Modifier
.width(size.div(3.5f)) .width(size.div(3.5f))
.height(size.div(3.5f)) .height(size.div(3.5f))
.align(Alignment.TopEnd), .align(Alignment.TopEnd)
contentAlignment = Alignment.Center }
) {
// Background for the transparent checkmark val myIconBackgroundModifier = remember {
Box(
Modifier Modifier
.clip(CircleShape) .clip(CircleShape)
.fillMaxSize(0.6f) .fillMaxSize(0.6f)
.align(Alignment.Center) .align(Alignment.Center)
.background(MaterialTheme.colors.background) }
val myIconModifier = remember {
Modifier.fillMaxSize()
}
Box(myIconBoxModifier, contentAlignment = Alignment.Center) {
Box(
myIconBackgroundModifier.background(MaterialTheme.colors.background)
) )
Icon( Icon(
painter = painterResource(R.drawable.ic_verified), painter = painterResource(R.drawable.ic_verified),
stringResource(id = R.string.following), stringResource(id = R.string.following),
modifier = Modifier.fillMaxSize(), modifier = myIconModifier,
tint = Following tint = Following
) )
} }

View File

@ -68,7 +68,7 @@ import kotlin.math.roundToInt
@Composable @Composable
fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel, navController: NavController) { fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel, navController: NavController) {
val accountState by accountViewModel.accountLiveData.observeAsState() 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) val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
@ -123,13 +123,21 @@ fun ReplyReaction(
onPress: () -> Unit onPress: () -> Unit
) { ) {
val repliesState by baseNote.live().replies.observeAsState() 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 context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val iconButtonModifier = remember {
Modifier.size(iconSize)
}
val iconModifier = remember {
Modifier.size(iconSize)
}
IconButton( IconButton(
modifier = Modifier.size(iconSize), modifier = iconButtonModifier,
onClick = { onClick = {
if (accountViewModel.isWriteable()) { if (accountViewModel.isWriteable()) {
onPress() onPress()
@ -147,7 +155,7 @@ fun ReplyReaction(
Icon( Icon(
painter = painterResource(R.drawable.ic_comment), painter = painterResource(R.drawable.ic_comment),
null, null,
modifier = Modifier.size(iconSize), modifier = iconModifier,
tint = grayTint tint = grayTint
) )
} }
@ -162,7 +170,7 @@ fun ReplyReaction(
} }
@Composable @Composable
public fun BoostReaction( fun BoostReaction(
baseNote: Note, baseNote: Note,
grayTint: Color, grayTint: Color,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
@ -170,17 +178,32 @@ public fun BoostReaction(
onQuotePress: () -> Unit onQuotePress: () -> Unit
) { ) {
val boostsState by baseNote.live().boosts.observeAsState() 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 context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var wantsToBoost by remember { mutableStateOf(false) } var wantsToBoost by remember { mutableStateOf(false) }
val iconButtonModifier = remember {
Modifier.size(iconSize)
}
val iconModifier = remember {
Modifier.size(iconSize)
}
IconButton( IconButton(
modifier = Modifier.then(Modifier.size(iconSize)), modifier = iconButtonModifier,
onClick = { onClick = {
if (accountViewModel.isWriteable()) { if (isWriteable) {
if (accountViewModel.hasBoosted(baseNote)) { if (hasBoosted) {
accountViewModel.deleteBoostsTo(baseNote) accountViewModel.deleteBoostsTo(baseNote)
} else { } else {
wantsToBoost = true wantsToBoost = true
@ -210,25 +233,16 @@ public fun BoostReaction(
) )
} }
if (boostedNote?.isBoostedBy(accountViewModel.userProfile()) == true) {
Icon( Icon(
painter = painterResource(R.drawable.ic_retweeted), painter = painterResource(R.drawable.ic_retweeted),
null, null,
modifier = Modifier.size(iconSize), modifier = iconModifier,
tint = Color.Unspecified tint = if (wasBoostedByLoggedIn) Color.Unspecified else grayTint
) )
} else {
Icon(
painter = painterResource(R.drawable.ic_retweet),
null,
modifier = Modifier.size(iconSize),
tint = grayTint
)
}
} }
Text( Text(
" ${showCount(boostedNote?.boosts?.size)}", " $boostCount",
fontSize = 14.sp, fontSize = 14.sp,
color = grayTint color = grayTint
) )
@ -243,16 +257,30 @@ fun LikeReaction(
heartSize: Dp = 16.dp heartSize: Dp = 16.dp
) { ) {
val reactionsState by baseNote.live().reactions.observeAsState() 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 context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val iconButtonModifier = remember {
Modifier.size(iconSize)
}
val iconModifier = remember {
Modifier.size(heartSize)
}
IconButton( IconButton(
modifier = Modifier.then(Modifier.size(iconSize)), modifier = iconButtonModifier,
onClick = { onClick = {
if (accountViewModel.isWriteable()) { if (isWriteable) {
if (accountViewModel.hasReactedTo(baseNote)) { if (hasReacted) {
accountViewModel.deleteReactionTo(baseNote) accountViewModel.deleteReactionTo(baseNote)
} else { } else {
accountViewModel.reactTo(baseNote) accountViewModel.reactTo(baseNote)
@ -268,25 +296,25 @@ fun LikeReaction(
} }
} }
) { ) {
if (reactedNote.isReactedBy(accountViewModel.userProfile())) { if (wasReactedByLoggedIn) {
Icon( Icon(
painter = painterResource(R.drawable.ic_liked), painter = painterResource(R.drawable.ic_liked),
null, null,
modifier = Modifier.size(heartSize), modifier = iconModifier,
tint = Color.Unspecified tint = Color.Unspecified
) )
} else { } else {
Icon( Icon(
painter = painterResource(R.drawable.ic_like), painter = painterResource(R.drawable.ic_like),
null, null,
modifier = Modifier.size(heartSize), modifier = iconModifier,
tint = grayTint tint = grayTint
) )
} }
} }
Text( Text(
" ${showCount(reactedNote.reactions.size)}", " $reactionCount",
fontSize = 14.sp, fontSize = 14.sp,
color = grayTint color = grayTint
) )
@ -472,6 +500,19 @@ public fun ViewCountReaction(
numberSize: Dp = 24.dp numberSize: Dp = 24.dp
) { ) {
val uri = LocalUriHandler.current 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( IconButton(
modifier = Modifier.size(iconSize), modifier = Modifier.size(iconSize),
@ -480,21 +521,25 @@ public fun ViewCountReaction(
Icon( Icon(
imageVector = Icons.Outlined.BarChart, imageVector = Icons.Outlined.BarChart,
null, null,
modifier = Modifier.size(barChartSize), modifier = iconButtonModifier,
tint = grayTint tint = grayTint
) )
} }
Row() { val request = remember {
AsyncImage( ImageRequest.Builder(context)
model = ImageRequest.Builder(LocalContext.current)
.data("https://counter.amethyst.social/$idHex.svg?label=+&color=00000000") .data("https://counter.amethyst.social/$idHex.svg?label=+&color=00000000")
.diskCachePolicy(CachePolicy.DISABLED) .diskCachePolicy(CachePolicy.DISABLED)
.memoryCachePolicy(CachePolicy.ENABLED) .memoryCachePolicy(CachePolicy.ENABLED)
.build(), .build()
}
Row {
AsyncImage(
model = request,
contentDescription = stringResource(R.string.view_count), contentDescription = stringResource(R.string.view_count),
modifier = Modifier.height(numberSize), modifier = iconModifier,
colorFilter = ColorFilter.tint(grayTint) colorFilter = colorFilter
) )
} }
} }