Moves state assignments to the main thread.

This commit is contained in:
Vitor Pamplona 2023-06-27 12:05:11 -04:00
parent fa7ccdfc20
commit 50f2d18b61
7 changed files with 260 additions and 158 deletions

View File

@ -39,7 +39,10 @@ fun CashuPreview(cashutoken: String, accountViewModel: AccountViewModel) {
LaunchedEffect(key1 = cashutoken) {
launch(Dispatchers.IO) {
cachuData = CashuProcessor().parse(cashutoken)
val newCachuData = CashuProcessor().parse(cashutoken)
launch(Dispatchers.Main) {
cachuData = newCachuData
}
}
}

View File

@ -159,7 +159,6 @@ data class RichTextViewerState(
val customEmoji: ImmutableMap<String, String>
)
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun RenderRegular(
content: String,

View File

@ -1,5 +1,6 @@
package com.vitorpamplona.amethyst.ui.note
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@ -59,6 +60,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.amethyst.ui.theme.WidthAuthorPictureModifierWithPadding
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
import com.vitorpamplona.amethyst.ui.theme.overPictureBackground
import kotlinx.collections.immutable.ImmutableList
@ -90,7 +92,9 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
}
if (backgroundColor.value != newBackgroundColor) {
backgroundColor.value = newBackgroundColor
launch(Dispatchers.Main) {
backgroundColor.value = newBackgroundColor
}
}
}
}
@ -120,7 +124,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
Galeries(multiSetCard, backgroundColor, nav, accountViewModel)
Row(remember { Modifier.fillMaxWidth() }) {
Spacer(modifier = remember { Modifier.width(65.dp) })
Spacer(modifier = WidthAuthorPictureModifierWithPadding)
NoteCompose(
baseNote = baseNote,
@ -336,28 +340,33 @@ private fun AuthorPictureAndComment(
val amount = (zapEvent?.event as? LnZapEvent)?.amount
if (decryptedContent != null) {
val newAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey)
content.value = ZapAmountCommentNotification(newAuthor, decryptedContent.content.ifBlank { null }, showAmountAxis(amount))
val newState = ZapAmountCommentNotification(newAuthor, decryptedContent.content.ifBlank { null }, showAmountAxis(amount))
launch(Dispatchers.Main) { content.value = newState }
} else {
if (!zapRequest.event?.content().isNullOrBlank() || amount != null) {
content.value = ZapAmountCommentNotification(zapRequest.author, zapRequest.event?.content()?.ifBlank { null }, showAmountAxis(amount))
val newState = ZapAmountCommentNotification(zapRequest.author, zapRequest.event?.content()?.ifBlank { null }, showAmountAxis(amount))
launch(Dispatchers.Main) { content.value = newState }
}
}
}
}
}
Row(
modifier = Modifier.clickable {
nav("User/${content.value.user?.pubkeyHex}")
},
verticalAlignment = Alignment.CenterVertically
) {
AuthorPictureAndComment(
authorComment = content,
backgroundColor = backgroundColor,
nav = nav,
accountViewModel = accountViewModel
)
Crossfade(targetState = content) {
Row(
modifier = Modifier.clickable {
nav("User/${it.value.user?.pubkeyHex}")
},
verticalAlignment = Alignment.CenterVertically
) {
AuthorPictureAndComment(
authorComment = it,
backgroundColor = backgroundColor,
nav = nav,
accountViewModel = accountViewModel
)
}
}
}
@ -365,7 +374,9 @@ val amountBoxModifier = Modifier
.fillMaxSize()
.clip(shape = CircleShape)
val textBoxModifier = Modifier.padding(start = 5.dp).fillMaxWidth()
val textBoxModifier = Modifier
.padding(start = 5.dp)
.fillMaxWidth()
val simpleModifier = Modifier
@ -562,7 +573,9 @@ fun WatchUserMetadata(userBase: User, onMetadataChanges: (UserMetadata) -> Unit)
LaunchedEffect(key1 = userState) {
launch(Dispatchers.Default) {
userState?.user?.info?.let {
onMetadataChanges(it)
launch(Dispatchers.Main) {
onMetadataChanges(it)
}
}
}
}

View File

@ -150,7 +150,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.LiveFlag
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ScheduledFlag
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import com.vitorpamplona.amethyst.ui.theme.DiviserThickness
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.Following
@ -188,7 +188,6 @@ import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import nostr.postr.toNpub
@ -261,14 +260,9 @@ fun CheckHiddenNoteCompose(
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val accountState by accountViewModel.accountLiveData.observeAsState()
val isHidden by remember(accountState) {
derivedStateOf {
val isSensitive = note.event?.isSensitive() ?: false
accountState?.account?.isHidden(note.author!!) == true || (isSensitive && accountState?.account?.showSensitiveContent == false)
}
}
val isHidden by accountViewModel.accountLiveData.map {
accountViewModel.isNoteHidden(note)
}.distinctUntilChanged().observeAsState(accountViewModel.isNoteHidden(note))
Crossfade(targetState = isHidden) {
if (!it) {
@ -324,8 +318,9 @@ fun LoadedNoteCompose(
WatchForReports(note, accountViewModel) { newIsAcceptable, newCanPreview, newRelevantReports ->
if (newIsAcceptable != state.isAcceptable || newCanPreview != state.canPreview) {
val newState = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports)
scope.launch(Dispatchers.Main) {
state = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports)
state = newState
}
}
}
@ -407,32 +402,7 @@ fun WatchForReports(
LaunchedEffect(key1 = noteReportsState, key2 = userFollowsState) {
launch(Dispatchers.Default) {
accountViewModel.account.let { loggedIn ->
val isFromLoggedIn = note.author?.pubkeyHex == loggedIn.userProfile().pubkeyHex
val isFromLoggedInFollow = note.author?.let { loggedIn.userProfile().isFollowingCached(it) } ?: true
if (isFromLoggedIn || isFromLoggedInFollow) {
// No need to process if from trusted people
onChange(true, true, persistentSetOf())
} else {
val newCanPreview = noteReportsState?.note?.hasAnyReports() != true
val newIsAcceptable = noteReportsState?.note?.let {
loggedIn.isAcceptable(it)
} ?: true
if (newCanPreview && newIsAcceptable) {
// No need to process reports if nothing is wrong
onChange(true, true, persistentSetOf())
} else {
val newRelevantReports = noteReportsState?.note?.let {
loggedIn.getRelevantReports(it)
} ?: emptySet()
onChange(newIsAcceptable, newCanPreview, newRelevantReports.toImmutableSet())
}
}
}
accountViewModel.isNoteAcceptable(note, onChange)
}
}
}
@ -526,14 +496,18 @@ private fun CheckNewAndRenderNote(
}
if (newBackgroundColor != backgroundColor.value) {
backgroundColor.value = newBackgroundColor
launch(Dispatchers.Main) {
backgroundColor.value = newBackgroundColor
}
}
}
} ?: run {
val newBackgroundColor = parentBackgroundColor?.value ?: defaultBackgroundColor
if (newBackgroundColor != backgroundColor.value) {
backgroundColor.value = newBackgroundColor
launch(Dispatchers.Main) {
backgroundColor.value = newBackgroundColor
}
}
}
}
@ -611,11 +585,10 @@ fun InnerNoteWithReactions(
nav: (String) -> Unit
) {
val notBoostedNorQuote = !isBoostedNote && !isQuotedNote
val showSecondRow = baseNote.event !is RepostEvent && baseNote.event !is GenericRepostEvent && !isBoostedNote && !isQuotedNote
Row(
modifier = remember {
Modifier
Modifier.fillMaxWidth()
.padding(
start = if (!isBoostedNote) 12.dp else 0.dp,
end = if (!isBoostedNote) 12.dp else 0.dp,
@ -632,6 +605,7 @@ fun InnerNoteWithReactions(
}
Column(Modifier.fillMaxWidth()) {
val showSecondRow = baseNote.event !is RepostEvent && baseNote.event !is GenericRepostEvent && !isBoostedNote && !isQuotedNote
NoteBody(
baseNote = baseNote,
showAuthorPicture = isQuotedNote,
@ -666,7 +640,7 @@ fun InnerNoteWithReactions(
if (notBoostedNorQuote) {
Divider(
thickness = DiviserThickness
thickness = DividerThickness
)
}
}
@ -1968,10 +1942,10 @@ private fun RenderAuthorImages(
nav: (String) -> Unit,
accountViewModel: AccountViewModel
) {
val isRepost = baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent
NoteAuthorPicture(baseNote, nav, accountViewModel, Size55dp)
val isRepost = baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent
if (isRepost) {
RepostNoteAuthorPicture(baseNote, accountViewModel, nav)
}
@ -1997,7 +1971,10 @@ fun LoadChannel(baseChannelHex: String, content: @Composable (Channel) -> Unit)
if (channel == null) {
LaunchedEffect(key1 = baseChannelHex) {
launch(Dispatchers.IO) {
channel = LocalCache.checkGetOrCreateChannel(baseChannelHex)
val newChannel = LocalCache.checkGetOrCreateChannel(baseChannelHex)
launch(Dispatchers.Main) {
channel = newChannel
}
}
}
}
@ -2108,7 +2085,10 @@ private fun DisplayQuoteAuthor(
LaunchedEffect(Unit) {
if (userBase == null) {
launch(Dispatchers.IO) {
userBase = LocalCache.checkGetOrCreateUser(authorHex)
val newUserBase = LocalCache.checkGetOrCreateUser(authorHex)
launch(Dispatchers.Main) {
userBase = newUserBase
}
}
}
}
@ -2181,7 +2161,9 @@ fun DisplayFollowingHashtagsInPost(
val newFirstTag = noteEvent.firstIsTaggedHashes(followingTags)
if (firstTag != newFirstTag) {
firstTag = newFirstTag
launch(Dispatchers.Main) {
firstTag = newFirstTag
}
}
}
}
@ -2328,7 +2310,9 @@ private fun RenderPledgeAmount(
}
val newHasPledge = repliesState?.note?.hasPledgeBy(accountViewModel.userProfile()) == true
if (hasPledge != newHasPledge) {
hasPledge = newHasPledge
launch(Dispatchers.Main) {
hasPledge = newHasPledge
}
}
}
}
@ -2379,7 +2363,9 @@ fun BadgeDisplay(baseNote: Note) {
darkColors().onBackground
}
backgroundFromImage = Pair(colorFromImage, textBackground)
launch(Dispatchers.Main) {
backgroundFromImage = Pair(colorFromImage, textBackground)
}
}
}
}
@ -2475,23 +2461,28 @@ fun FileHeaderDisplay(note: Note) {
val removedParamsFromUrl = fullUrl.split("?")[0].lowercase()
val isImage = imageExtensions.any { removedParamsFromUrl.endsWith(it) }
val uri = "nostr:" + note.toNEvent()
content = if (isImage) {
val newContent = if (isImage) {
ZoomableUrlImage(fullUrl, description, hash, blurHash, dimensions, uri)
} else {
ZoomableUrlVideo(fullUrl, description, hash, uri)
}
launch(Dispatchers.Main) {
content = newContent
}
}
}
}
content?.let {
ZoomableContentView(content = it)
Crossfade(targetState = content) {
if (it != null) {
ZoomableContentView(content = it)
}
}
}
@Composable
fun FileStorageHeaderDisplay(baseNote: Note) {
val appContext = LocalContext.current.applicationContext
val eventHeader = (baseNote.event as? FileStorageHeaderEvent) ?: return
var fileNote by remember { mutableStateOf<Note?>(null) }
@ -2499,52 +2490,75 @@ fun FileStorageHeaderDisplay(baseNote: Note) {
if (fileNote == null) {
LaunchedEffect(key1 = eventHeader.id) {
launch(Dispatchers.IO) {
fileNote = eventHeader.dataEventId()?.let { LocalCache.checkGetOrCreateNote(it) }
val newFileNote = eventHeader.dataEventId()?.let { LocalCache.checkGetOrCreateNote(it) }
launch(Dispatchers.Main) {
fileNote = newFileNote
}
}
}
}
fileNote?.let { fileNote2 ->
val noteState by fileNote2.live().metadata.observeAsState()
val note = remember(noteState) { noteState?.note }
Crossfade(targetState = fileNote) {
if (it != null) {
RenderNIP95(it, eventHeader, baseNote)
}
}
}
var content by remember { mutableStateOf<ZoomableContent?>(null) }
@Composable
private fun RenderNIP95(
it: Note,
eventHeader: FileStorageHeaderEvent,
baseNote: Note
) {
val appContext = LocalContext.current.applicationContext
if (content == null) {
LaunchedEffect(key1 = eventHeader.id, key2 = noteState, key3 = note?.event) {
launch(Dispatchers.IO) {
val uri = "nostr:" + baseNote.toNEvent()
val localDir = note?.idHex?.let { File(File(appContext.externalCacheDir, "NIP95"), it) }
val blurHash = eventHeader.blurhash()
val dimensions = eventHeader.dimensions()
val description = eventHeader.content
val mimeType = eventHeader.mimeType()
val noteState by it.live().metadata.observeAsState()
val note = remember(noteState) { noteState?.note }
content = if (mimeType?.startsWith("image") == true) {
ZoomableLocalImage(
localFile = localDir,
mimeType = mimeType,
description = description,
blurhash = blurHash,
dim = dimensions,
isVerified = true,
uri = uri
)
} else {
ZoomableLocalVideo(
localFile = localDir,
mimeType = mimeType,
description = description,
dim = dimensions,
isVerified = true,
uri = uri
)
}
var content by remember { mutableStateOf<ZoomableContent?>(null) }
if (content == null) {
LaunchedEffect(key1 = eventHeader.id, key2 = noteState, key3 = note?.event) {
launch(Dispatchers.IO) {
val uri = "nostr:" + baseNote.toNEvent()
val localDir =
note?.idHex?.let { File(File(appContext.externalCacheDir, "NIP95"), it) }
val blurHash = eventHeader.blurhash()
val dimensions = eventHeader.dimensions()
val description = eventHeader.content
val mimeType = eventHeader.mimeType()
val newContent = if (mimeType?.startsWith("image") == true) {
ZoomableLocalImage(
localFile = localDir,
mimeType = mimeType,
description = description,
blurhash = blurHash,
dim = dimensions,
isVerified = true,
uri = uri
)
} else {
ZoomableLocalVideo(
localFile = localDir,
mimeType = mimeType,
description = description,
dim = dimensions,
isVerified = true,
uri = uri
)
}
launch(Dispatchers.Main) {
content = newContent
}
}
}
}
content?.let {
Crossfade(targetState = content) {
if (it != null) {
ZoomableContentView(content = it)
}
}
@ -2892,14 +2906,16 @@ private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav:
val scope = rememberCoroutineScope()
WatchRelayLists(baseNote) { relayList ->
scope.launch(Dispatchers.Main) {
if (!equalImmutableLists(relayList, lazyRelayList)) {
if (!equalImmutableLists(relayList, lazyRelayList)) {
scope.launch(Dispatchers.Main) {
lazyRelayList = relayList
shortRelayList = relayList.take(3).toImmutableList()
}
}
val nextShowMore = relayList.size > 3
if (nextShowMore != showShowMore) {
val nextShowMore = relayList.size > 3
if (nextShowMore != showShowMore) {
scope.launch(Dispatchers.Main) {
// only triggers recomposition when actually different
showShowMore = nextShowMore
}
@ -3393,16 +3409,20 @@ fun WatchBookmarksFollowsAndAccount(note: Note, accountViewModel: AccountViewMod
LaunchedEffect(key1 = followState, key2 = bookmarkState, key3 = accountState) {
launch(Dispatchers.IO) {
onNew(
DropDownParams(
isFollowingAuthor = accountViewModel.isFollowing(note.author),
isPrivateBookmarkNote = accountViewModel.isInPrivateBookmarks(note),
isPublicBookmarkNote = accountViewModel.isInPublicBookmarks(note),
isLoggedUser = accountViewModel.isLoggedUser(note.author),
isSensitive = note.event?.isSensitive() ?: false,
showSensitiveContent = accountState?.account?.showSensitiveContent
)
val newState = DropDownParams(
isFollowingAuthor = accountViewModel.isFollowing(note.author),
isPrivateBookmarkNote = accountViewModel.isInPrivateBookmarks(note),
isPublicBookmarkNote = accountViewModel.isInPublicBookmarks(note),
isLoggedUser = accountViewModel.isLoggedUser(note.author),
isSensitive = note.event?.isSensitive() ?: false,
showSensitiveContent = accountState?.account?.showSensitiveContent
)
launch(Dispatchers.Main) {
onNew(
newState
)
}
}
}
}

View File

@ -64,6 +64,8 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map
import coil.compose.AsyncImage
@ -148,7 +150,7 @@ private fun InnerReactionRow(
modifier = ReactionRowExpandButton
) {
Row(verticalAlignment = CenterVertically) {
ExpandButton(baseNote) {
WatchReactionsZapsBoostsAndDisplayIfExists(baseNote) {
RenderShowIndividualReactionsButton(wantsToSeeReactions)
}
}
@ -163,6 +165,7 @@ private fun InnerReactionRow(
ReplyReactionWithDialog(baseNote, MaterialTheme.colors.placeholderText, accountViewModel, nav)
}
}
Column(
verticalArrangement = Arrangement.Center,
modifier = remember { Modifier.weight(1f) }
@ -171,6 +174,7 @@ private fun InnerReactionRow(
BoostWithDialog(baseNote, MaterialTheme.colors.placeholderText, accountViewModel, nav)
}
}
Column(
verticalArrangement = Arrangement.Center,
modifier = remember { Modifier.weight(1f) }
@ -179,6 +183,7 @@ private fun InnerReactionRow(
LikeReaction(baseNote, MaterialTheme.colors.placeholderText, accountViewModel)
}
}
Column(
verticalArrangement = Arrangement.Center,
modifier = remember { Modifier.weight(1f) }
@ -187,6 +192,7 @@ private fun InnerReactionRow(
ZapReaction(baseNote, MaterialTheme.colors.placeholderText, accountViewModel)
}
}
Column(
verticalArrangement = Arrangement.Center,
modifier = remember { Modifier.weight(1f) }
@ -299,22 +305,44 @@ fun RenderZapRaiser(baseNote: Note, zapraiserAmount: Long, details: Boolean, acc
}
@Composable
private fun ExpandButton(baseNote: Note, content: @Composable () -> Unit) {
val zapsState by baseNote.live().zaps.observeAsState()
val boostsState by baseNote.live().boosts.observeAsState()
val reactionsState by baseNote.live().reactions.observeAsState()
private fun WatchReactionsZapsBoostsAndDisplayIfExists(baseNote: Note, content: @Composable () -> Unit) {
val hasReactions by baseNote.live().zaps.combineWith(
liveData1 = baseNote.live().boosts,
liveData2 = baseNote.live().reactions,
block = { zapsState, boostsState, reactionsState ->
zapsState?.note?.zaps?.isNotEmpty() == true ||
boostsState?.note?.boosts?.isNotEmpty() == true ||
reactionsState?.note?.reactions?.isNotEmpty() == true
}
).observeAsState(
baseNote.zaps.isNotEmpty() ||
baseNote.boosts.isNotEmpty() ||
baseNote.reactions.isNotEmpty()
)
val hasReactions by remember(zapsState, boostsState, reactionsState) {
derivedStateOf {
baseNote.zaps.isNotEmpty() ||
baseNote.boosts.isNotEmpty() ||
baseNote.reactions.isNotEmpty()
Crossfade(targetState = hasReactions) {
if (it) {
content()
}
}
}
if (hasReactions) {
content()
fun <T, K, P, R> LiveData<T>.combineWith(
liveData1: LiveData<K>,
liveData2: LiveData<P>,
block: (T?, K?, P?) -> R
): LiveData<R> {
val result = MediatorLiveData<R>()
result.addSource(this) {
result.value = block(this.value, liveData1.value, liveData2.value)
}
result.addSource(liveData1) {
result.value = block(this.value, liveData1.value, liveData2.value)
}
result.addSource(liveData2) {
result.value = block(this.value, liveData1.value, liveData2.value)
}
return result
}
@Composable
@ -325,20 +353,22 @@ private fun RenderShowIndividualReactionsButton(wantsToSeeReactions: MutableStat
},
modifier = Size20Modifier
) {
if (wantsToSeeReactions.value) {
Icon(
imageVector = Icons.Default.ExpandLess,
null,
modifier = Size22Modifier,
tint = MaterialTheme.colors.subtleButton
)
} else {
Icon(
imageVector = Icons.Default.ExpandMore,
null,
modifier = Size22Modifier,
tint = MaterialTheme.colors.subtleButton
)
Crossfade(targetState = wantsToSeeReactions.value) {
if (it) {
Icon(
imageVector = Icons.Default.ExpandLess,
null,
modifier = Size22Modifier,
tint = MaterialTheme.colors.subtleButton
)
} else {
Icon(
imageVector = Icons.Default.ExpandMore,
null,
modifier = Size22Modifier,
tint = MaterialTheme.colors.subtleButton
)
}
}
}
}
@ -503,7 +533,7 @@ fun ReplyReaction(
fun ReplyCounter(baseNote: Note, textColor: Color) {
val repliesState by baseNote.live().replies.map {
it.note.replies.size
}.observeAsState(0)
}.observeAsState(baseNote.replies.size)
SlidingAnimation(repliesState, textColor)
}
@ -521,16 +551,21 @@ private fun SlidingAnimation(baseCount: Int, textColor: Color) {
}
}
) { count ->
Text(
text = remember(count) { showCount(count) },
fontSize = Font14SP,
color = textColor,
modifier = HalfStartPadding,
maxLines = 1
)
TextCount(count, textColor)
}
}
@Composable
private fun TextCount(count: Int, textColor: Color) {
Text(
text = remember(count) { showCount(count) },
fontSize = Font14SP,
color = textColor,
modifier = HalfStartPadding,
maxLines = 1
)
}
@Composable
@OptIn(ExperimentalAnimationApi::class)
private fun SlidingAnimation(amount: String, textColor: Color) {
@ -634,7 +669,7 @@ fun BoostIcon(baseNote: Note, iconSize: Dp = Size20dp, grayTint: Color, accountV
fun BoostText(baseNote: Note, grayTint: Color) {
val boostState by baseNote.live().boosts.map {
it.note.boosts.size
}.distinctUntilChanged().observeAsState(0)
}.distinctUntilChanged().observeAsState(baseNote.boosts.size)
SlidingAnimation(boostState, grayTint)
}
@ -786,7 +821,7 @@ fun LikeText(baseNote: Note, grayTint: Color) {
val reactionsState by baseNote.live().reactions.observeAsState()
var reactionsCount by remember(baseNote) {
mutableStateOf(0)
mutableStateOf(baseNote.reactions.size)
}
LaunchedEffect(key1 = reactionsState) {

View File

@ -21,6 +21,9 @@ import com.vitorpamplona.amethyst.service.model.Event
import com.vitorpamplona.amethyst.service.model.LnZapEvent
import com.vitorpamplona.amethyst.service.model.PayInvoiceErrorResponse
import com.vitorpamplona.amethyst.service.model.ReportEvent
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -57,6 +60,11 @@ class AccountViewModel(val account: Account) : ViewModel() {
}
}
fun isNoteHidden(note: Note): Boolean {
val isSensitive = note.event?.isSensitive() ?: false
return account.isHidden(note.author!!) || (isSensitive && account.showSensitiveContent == false)
}
fun hasReactedTo(baseNote: Note, reaction: String): Boolean {
return account.hasReacted(baseNote, reaction)
}
@ -263,6 +271,29 @@ class AccountViewModel(val account: Account) : ViewModel() {
return account.defaultZapType
}
fun isNoteAcceptable(note: Note, onReady: (Boolean, Boolean, ImmutableSet<Note>) -> Unit) {
val isFromLoggedIn = note.author?.pubkeyHex == userProfile().pubkeyHex
val isFromLoggedInFollow = note.author?.let { userProfile().isFollowingCached(it) } ?: true
if (isFromLoggedIn || isFromLoggedInFollow) {
// No need to process if from trusted people
onReady(true, true, persistentSetOf())
} else {
val newCanPreview = !note.hasAnyReports()
val newIsAcceptable = account.isAcceptable(note)
if (newCanPreview && newIsAcceptable) {
// No need to process reports if nothing is wrong
onReady(true, true, persistentSetOf())
} else {
val newRelevantReports = account.getRelevantReports(note)
onReady(newIsAcceptable, newCanPreview, newRelevantReports.toImmutableSet())
}
}
}
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <AccountViewModel : ViewModel> create(modelClass: Class<AccountViewModel>): AccountViewModel {
return AccountViewModel(account) as AccountViewModel

View File

@ -73,8 +73,9 @@ val ReactionRowZapraiserSize = Modifier.defaultMinSize(minHeight = 4.dp).fillMax
val ReactionRowExpandButton = Modifier.width(65.dp).padding(start = 31.dp)
val WidthAuthorPictureModifier = Modifier.width(55.dp)
val WidthAuthorPictureModifierWithPadding = Modifier.width(65.dp)
val DiviserThickness = 0.25.dp
val DividerThickness = 0.25.dp
val ReactionRowHeight = Modifier.height(24.dp).padding(start = 10.dp)
val ReactionRowHeightChat = Modifier.height(25.dp)