mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2024-09-29 08:20:51 +00:00
Moves state assignments to the main thread.
This commit is contained in:
parent
fa7ccdfc20
commit
50f2d18b61
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,7 +159,6 @@ data class RichTextViewerState(
|
||||
val customEmoji: ImmutableMap<String, String>
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun RenderRegular(
|
||||
content: String,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user