Moves state assignments to the main thread.
This commit is contained in:
@@ -39,7 +39,10 @@ fun CashuPreview(cashutoken: String, accountViewModel: AccountViewModel) {
|
|||||||
|
|
||||||
LaunchedEffect(key1 = cashutoken) {
|
LaunchedEffect(key1 = cashutoken) {
|
||||||
launch(Dispatchers.IO) {
|
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>
|
val customEmoji: ImmutableMap<String, String>
|
||||||
)
|
)
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RenderRegular(
|
private fun RenderRegular(
|
||||||
content: String,
|
content: String,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
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.screen.loggedIn.showAmountAxis
|
||||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
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.newItemBackgroundColor
|
||||||
import com.vitorpamplona.amethyst.ui.theme.overPictureBackground
|
import com.vitorpamplona.amethyst.ui.theme.overPictureBackground
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
@@ -90,10 +92,12 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (backgroundColor.value != newBackgroundColor) {
|
if (backgroundColor.value != newBackgroundColor) {
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
backgroundColor.value = newBackgroundColor
|
backgroundColor.value = newBackgroundColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val columnModifier = remember(backgroundColor.value) {
|
val columnModifier = remember(backgroundColor.value) {
|
||||||
Modifier
|
Modifier
|
||||||
@@ -120,7 +124,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
|
|||||||
Galeries(multiSetCard, backgroundColor, nav, accountViewModel)
|
Galeries(multiSetCard, backgroundColor, nav, accountViewModel)
|
||||||
|
|
||||||
Row(remember { Modifier.fillMaxWidth() }) {
|
Row(remember { Modifier.fillMaxWidth() }) {
|
||||||
Spacer(modifier = remember { Modifier.width(65.dp) })
|
Spacer(modifier = WidthAuthorPictureModifierWithPadding)
|
||||||
|
|
||||||
NoteCompose(
|
NoteCompose(
|
||||||
baseNote = baseNote,
|
baseNote = baseNote,
|
||||||
@@ -336,36 +340,43 @@ private fun AuthorPictureAndComment(
|
|||||||
val amount = (zapEvent?.event as? LnZapEvent)?.amount
|
val amount = (zapEvent?.event as? LnZapEvent)?.amount
|
||||||
if (decryptedContent != null) {
|
if (decryptedContent != null) {
|
||||||
val newAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey)
|
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 {
|
} else {
|
||||||
if (!zapRequest.event?.content().isNullOrBlank() || amount != null) {
|
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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Crossfade(targetState = content) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
nav("User/${content.value.user?.pubkeyHex}")
|
nav("User/${it.value.user?.pubkeyHex}")
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
AuthorPictureAndComment(
|
AuthorPictureAndComment(
|
||||||
authorComment = content,
|
authorComment = it,
|
||||||
backgroundColor = backgroundColor,
|
backgroundColor = backgroundColor,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
accountViewModel = accountViewModel
|
accountViewModel = accountViewModel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val amountBoxModifier = Modifier
|
val amountBoxModifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.clip(shape = CircleShape)
|
.clip(shape = CircleShape)
|
||||||
|
|
||||||
val textBoxModifier = Modifier.padding(start = 5.dp).fillMaxWidth()
|
val textBoxModifier = Modifier
|
||||||
|
.padding(start = 5.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
|
||||||
val simpleModifier = Modifier
|
val simpleModifier = Modifier
|
||||||
|
|
||||||
@@ -562,8 +573,10 @@ fun WatchUserMetadata(userBase: User, onMetadataChanges: (UserMetadata) -> Unit)
|
|||||||
LaunchedEffect(key1 = userState) {
|
LaunchedEffect(key1 = userState) {
|
||||||
launch(Dispatchers.Default) {
|
launch(Dispatchers.Default) {
|
||||||
userState?.user?.info?.let {
|
userState?.user?.info?.let {
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
onMetadataChanges(it)
|
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.ReportNoteDialog
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ScheduledFlag
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ScheduledFlag
|
||||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
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.DoubleHorzSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Following
|
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.persistentListOf
|
||||||
import kotlinx.collections.immutable.persistentSetOf
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableSet
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import nostr.postr.toNpub
|
import nostr.postr.toNpub
|
||||||
@@ -261,14 +260,9 @@ fun CheckHiddenNoteCompose(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val isHidden by accountViewModel.accountLiveData.map {
|
||||||
|
accountViewModel.isNoteHidden(note)
|
||||||
val isHidden by remember(accountState) {
|
}.distinctUntilChanged().observeAsState(accountViewModel.isNoteHidden(note))
|
||||||
derivedStateOf {
|
|
||||||
val isSensitive = note.event?.isSensitive() ?: false
|
|
||||||
accountState?.account?.isHidden(note.author!!) == true || (isSensitive && accountState?.account?.showSensitiveContent == false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Crossfade(targetState = isHidden) {
|
Crossfade(targetState = isHidden) {
|
||||||
if (!it) {
|
if (!it) {
|
||||||
@@ -324,8 +318,9 @@ fun LoadedNoteCompose(
|
|||||||
|
|
||||||
WatchForReports(note, accountViewModel) { newIsAcceptable, newCanPreview, newRelevantReports ->
|
WatchForReports(note, accountViewModel) { newIsAcceptable, newCanPreview, newRelevantReports ->
|
||||||
if (newIsAcceptable != state.isAcceptable || newCanPreview != state.canPreview) {
|
if (newIsAcceptable != state.isAcceptable || newCanPreview != state.canPreview) {
|
||||||
|
val newState = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports)
|
||||||
scope.launch(Dispatchers.Main) {
|
scope.launch(Dispatchers.Main) {
|
||||||
state = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports)
|
state = newState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -407,32 +402,7 @@ fun WatchForReports(
|
|||||||
|
|
||||||
LaunchedEffect(key1 = noteReportsState, key2 = userFollowsState) {
|
LaunchedEffect(key1 = noteReportsState, key2 = userFollowsState) {
|
||||||
launch(Dispatchers.Default) {
|
launch(Dispatchers.Default) {
|
||||||
accountViewModel.account.let { loggedIn ->
|
accountViewModel.isNoteAcceptable(note, onChange)
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -526,18 +496,22 @@ private fun CheckNewAndRenderNote(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (newBackgroundColor != backgroundColor.value) {
|
if (newBackgroundColor != backgroundColor.value) {
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
backgroundColor.value = newBackgroundColor
|
backgroundColor.value = newBackgroundColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} ?: run {
|
} ?: run {
|
||||||
val newBackgroundColor = parentBackgroundColor?.value ?: defaultBackgroundColor
|
val newBackgroundColor = parentBackgroundColor?.value ?: defaultBackgroundColor
|
||||||
|
|
||||||
if (newBackgroundColor != backgroundColor.value) {
|
if (newBackgroundColor != backgroundColor.value) {
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
backgroundColor.value = newBackgroundColor
|
backgroundColor.value = newBackgroundColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ClickableNote(
|
ClickableNote(
|
||||||
baseNote = baseNote,
|
baseNote = baseNote,
|
||||||
@@ -611,11 +585,10 @@ fun InnerNoteWithReactions(
|
|||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val notBoostedNorQuote = !isBoostedNote && !isQuotedNote
|
val notBoostedNorQuote = !isBoostedNote && !isQuotedNote
|
||||||
val showSecondRow = baseNote.event !is RepostEvent && baseNote.event !is GenericRepostEvent && !isBoostedNote && !isQuotedNote
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = remember {
|
modifier = remember {
|
||||||
Modifier
|
Modifier.fillMaxWidth()
|
||||||
.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,
|
||||||
@@ -632,6 +605,7 @@ fun InnerNoteWithReactions(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column(Modifier.fillMaxWidth()) {
|
Column(Modifier.fillMaxWidth()) {
|
||||||
|
val showSecondRow = baseNote.event !is RepostEvent && baseNote.event !is GenericRepostEvent && !isBoostedNote && !isQuotedNote
|
||||||
NoteBody(
|
NoteBody(
|
||||||
baseNote = baseNote,
|
baseNote = baseNote,
|
||||||
showAuthorPicture = isQuotedNote,
|
showAuthorPicture = isQuotedNote,
|
||||||
@@ -666,7 +640,7 @@ fun InnerNoteWithReactions(
|
|||||||
|
|
||||||
if (notBoostedNorQuote) {
|
if (notBoostedNorQuote) {
|
||||||
Divider(
|
Divider(
|
||||||
thickness = DiviserThickness
|
thickness = DividerThickness
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1968,10 +1942,10 @@ private fun RenderAuthorImages(
|
|||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val isRepost = baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent
|
|
||||||
|
|
||||||
NoteAuthorPicture(baseNote, nav, accountViewModel, Size55dp)
|
NoteAuthorPicture(baseNote, nav, accountViewModel, Size55dp)
|
||||||
|
|
||||||
|
val isRepost = baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent
|
||||||
|
|
||||||
if (isRepost) {
|
if (isRepost) {
|
||||||
RepostNoteAuthorPicture(baseNote, accountViewModel, nav)
|
RepostNoteAuthorPicture(baseNote, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
@@ -1997,7 +1971,10 @@ fun LoadChannel(baseChannelHex: String, content: @Composable (Channel) -> Unit)
|
|||||||
if (channel == null) {
|
if (channel == null) {
|
||||||
LaunchedEffect(key1 = baseChannelHex) {
|
LaunchedEffect(key1 = baseChannelHex) {
|
||||||
launch(Dispatchers.IO) {
|
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) {
|
LaunchedEffect(Unit) {
|
||||||
if (userBase == null) {
|
if (userBase == null) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
userBase = LocalCache.checkGetOrCreateUser(authorHex)
|
val newUserBase = LocalCache.checkGetOrCreateUser(authorHex)
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
userBase = newUserBase
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2181,10 +2161,12 @@ fun DisplayFollowingHashtagsInPost(
|
|||||||
val newFirstTag = noteEvent.firstIsTaggedHashes(followingTags)
|
val newFirstTag = noteEvent.firstIsTaggedHashes(followingTags)
|
||||||
|
|
||||||
if (firstTag != newFirstTag) {
|
if (firstTag != newFirstTag) {
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
firstTag = newFirstTag
|
firstTag = newFirstTag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
firstTag?.let {
|
firstTag?.let {
|
||||||
Column() {
|
Column() {
|
||||||
@@ -2328,10 +2310,12 @@ private fun RenderPledgeAmount(
|
|||||||
}
|
}
|
||||||
val newHasPledge = repliesState?.note?.hasPledgeBy(accountViewModel.userProfile()) == true
|
val newHasPledge = repliesState?.note?.hasPledgeBy(accountViewModel.userProfile()) == true
|
||||||
if (hasPledge != newHasPledge) {
|
if (hasPledge != newHasPledge) {
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
hasPledge = newHasPledge
|
hasPledge = newHasPledge
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (hasPledge) {
|
if (hasPledge) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -2379,10 +2363,12 @@ fun BadgeDisplay(baseNote: Note) {
|
|||||||
darkColors().onBackground
|
darkColors().onBackground
|
||||||
}
|
}
|
||||||
|
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
backgroundFromImage = Pair(colorFromImage, textBackground)
|
backgroundFromImage = Pair(colorFromImage, textBackground)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -2475,23 +2461,28 @@ fun FileHeaderDisplay(note: Note) {
|
|||||||
val removedParamsFromUrl = fullUrl.split("?")[0].lowercase()
|
val removedParamsFromUrl = fullUrl.split("?")[0].lowercase()
|
||||||
val isImage = imageExtensions.any { removedParamsFromUrl.endsWith(it) }
|
val isImage = imageExtensions.any { removedParamsFromUrl.endsWith(it) }
|
||||||
val uri = "nostr:" + note.toNEvent()
|
val uri = "nostr:" + note.toNEvent()
|
||||||
content = if (isImage) {
|
val newContent = if (isImage) {
|
||||||
ZoomableUrlImage(fullUrl, description, hash, blurHash, dimensions, uri)
|
ZoomableUrlImage(fullUrl, description, hash, blurHash, dimensions, uri)
|
||||||
} else {
|
} else {
|
||||||
ZoomableUrlVideo(fullUrl, description, hash, uri)
|
ZoomableUrlVideo(fullUrl, description, hash, uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
content = newContent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content?.let {
|
Crossfade(targetState = content) {
|
||||||
|
if (it != null) {
|
||||||
ZoomableContentView(content = it)
|
ZoomableContentView(content = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FileStorageHeaderDisplay(baseNote: Note) {
|
fun FileStorageHeaderDisplay(baseNote: Note) {
|
||||||
val appContext = LocalContext.current.applicationContext
|
|
||||||
val eventHeader = (baseNote.event as? FileStorageHeaderEvent) ?: return
|
val eventHeader = (baseNote.event as? FileStorageHeaderEvent) ?: return
|
||||||
|
|
||||||
var fileNote by remember { mutableStateOf<Note?>(null) }
|
var fileNote by remember { mutableStateOf<Note?>(null) }
|
||||||
@@ -2499,13 +2490,30 @@ fun FileStorageHeaderDisplay(baseNote: Note) {
|
|||||||
if (fileNote == null) {
|
if (fileNote == null) {
|
||||||
LaunchedEffect(key1 = eventHeader.id) {
|
LaunchedEffect(key1 = eventHeader.id) {
|
||||||
launch(Dispatchers.IO) {
|
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 ->
|
Crossfade(targetState = fileNote) {
|
||||||
val noteState by fileNote2.live().metadata.observeAsState()
|
if (it != null) {
|
||||||
|
RenderNIP95(it, eventHeader, baseNote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RenderNIP95(
|
||||||
|
it: Note,
|
||||||
|
eventHeader: FileStorageHeaderEvent,
|
||||||
|
baseNote: Note
|
||||||
|
) {
|
||||||
|
val appContext = LocalContext.current.applicationContext
|
||||||
|
|
||||||
|
val noteState by it.live().metadata.observeAsState()
|
||||||
val note = remember(noteState) { noteState?.note }
|
val note = remember(noteState) { noteState?.note }
|
||||||
|
|
||||||
var content by remember { mutableStateOf<ZoomableContent?>(null) }
|
var content by remember { mutableStateOf<ZoomableContent?>(null) }
|
||||||
@@ -2514,13 +2522,14 @@ fun FileStorageHeaderDisplay(baseNote: Note) {
|
|||||||
LaunchedEffect(key1 = eventHeader.id, key2 = noteState, key3 = note?.event) {
|
LaunchedEffect(key1 = eventHeader.id, key2 = noteState, key3 = note?.event) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
val uri = "nostr:" + baseNote.toNEvent()
|
val uri = "nostr:" + baseNote.toNEvent()
|
||||||
val localDir = note?.idHex?.let { File(File(appContext.externalCacheDir, "NIP95"), it) }
|
val localDir =
|
||||||
|
note?.idHex?.let { File(File(appContext.externalCacheDir, "NIP95"), it) }
|
||||||
val blurHash = eventHeader.blurhash()
|
val blurHash = eventHeader.blurhash()
|
||||||
val dimensions = eventHeader.dimensions()
|
val dimensions = eventHeader.dimensions()
|
||||||
val description = eventHeader.content
|
val description = eventHeader.content
|
||||||
val mimeType = eventHeader.mimeType()
|
val mimeType = eventHeader.mimeType()
|
||||||
|
|
||||||
content = if (mimeType?.startsWith("image") == true) {
|
val newContent = if (mimeType?.startsWith("image") == true) {
|
||||||
ZoomableLocalImage(
|
ZoomableLocalImage(
|
||||||
localFile = localDir,
|
localFile = localDir,
|
||||||
mimeType = mimeType,
|
mimeType = mimeType,
|
||||||
@@ -2540,11 +2549,16 @@ fun FileStorageHeaderDisplay(baseNote: Note) {
|
|||||||
uri = uri
|
uri = uri
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
content = newContent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content?.let {
|
Crossfade(targetState = content) {
|
||||||
|
if (it != null) {
|
||||||
ZoomableContentView(content = it)
|
ZoomableContentView(content = it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2892,14 +2906,16 @@ private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav:
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
WatchRelayLists(baseNote) { relayList ->
|
WatchRelayLists(baseNote) { relayList ->
|
||||||
scope.launch(Dispatchers.Main) {
|
|
||||||
if (!equalImmutableLists(relayList, lazyRelayList)) {
|
if (!equalImmutableLists(relayList, lazyRelayList)) {
|
||||||
|
scope.launch(Dispatchers.Main) {
|
||||||
lazyRelayList = relayList
|
lazyRelayList = relayList
|
||||||
shortRelayList = relayList.take(3).toImmutableList()
|
shortRelayList = relayList.take(3).toImmutableList()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val nextShowMore = relayList.size > 3
|
val nextShowMore = relayList.size > 3
|
||||||
if (nextShowMore != showShowMore) {
|
if (nextShowMore != showShowMore) {
|
||||||
|
scope.launch(Dispatchers.Main) {
|
||||||
// only triggers recomposition when actually different
|
// only triggers recomposition when actually different
|
||||||
showShowMore = nextShowMore
|
showShowMore = nextShowMore
|
||||||
}
|
}
|
||||||
@@ -3393,8 +3409,7 @@ fun WatchBookmarksFollowsAndAccount(note: Note, accountViewModel: AccountViewMod
|
|||||||
|
|
||||||
LaunchedEffect(key1 = followState, key2 = bookmarkState, key3 = accountState) {
|
LaunchedEffect(key1 = followState, key2 = bookmarkState, key3 = accountState) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
onNew(
|
val newState = DropDownParams(
|
||||||
DropDownParams(
|
|
||||||
isFollowingAuthor = accountViewModel.isFollowing(note.author),
|
isFollowingAuthor = accountViewModel.isFollowing(note.author),
|
||||||
isPrivateBookmarkNote = accountViewModel.isInPrivateBookmarks(note),
|
isPrivateBookmarkNote = accountViewModel.isInPrivateBookmarks(note),
|
||||||
isPublicBookmarkNote = accountViewModel.isInPublicBookmarks(note),
|
isPublicBookmarkNote = accountViewModel.isInPublicBookmarks(note),
|
||||||
@@ -3402,7 +3417,12 @@ fun WatchBookmarksFollowsAndAccount(note: Note, accountViewModel: AccountViewMod
|
|||||||
isSensitive = note.event?.isSensitive() ?: false,
|
isSensitive = note.event?.isSensitive() ?: false,
|
||||||
showSensitiveContent = accountState?.account?.showSensitiveContent
|
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.TextUnit
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Popup
|
import androidx.compose.ui.window.Popup
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MediatorLiveData
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
import androidx.lifecycle.distinctUntilChanged
|
||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
@@ -148,7 +150,7 @@ private fun InnerReactionRow(
|
|||||||
modifier = ReactionRowExpandButton
|
modifier = ReactionRowExpandButton
|
||||||
) {
|
) {
|
||||||
Row(verticalAlignment = CenterVertically) {
|
Row(verticalAlignment = CenterVertically) {
|
||||||
ExpandButton(baseNote) {
|
WatchReactionsZapsBoostsAndDisplayIfExists(baseNote) {
|
||||||
RenderShowIndividualReactionsButton(wantsToSeeReactions)
|
RenderShowIndividualReactionsButton(wantsToSeeReactions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,6 +165,7 @@ private fun InnerReactionRow(
|
|||||||
ReplyReactionWithDialog(baseNote, MaterialTheme.colors.placeholderText, accountViewModel, nav)
|
ReplyReactionWithDialog(baseNote, MaterialTheme.colors.placeholderText, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
modifier = remember { Modifier.weight(1f) }
|
modifier = remember { Modifier.weight(1f) }
|
||||||
@@ -171,6 +174,7 @@ private fun InnerReactionRow(
|
|||||||
BoostWithDialog(baseNote, MaterialTheme.colors.placeholderText, accountViewModel, nav)
|
BoostWithDialog(baseNote, MaterialTheme.colors.placeholderText, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
modifier = remember { Modifier.weight(1f) }
|
modifier = remember { Modifier.weight(1f) }
|
||||||
@@ -179,6 +183,7 @@ private fun InnerReactionRow(
|
|||||||
LikeReaction(baseNote, MaterialTheme.colors.placeholderText, accountViewModel)
|
LikeReaction(baseNote, MaterialTheme.colors.placeholderText, accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
modifier = remember { Modifier.weight(1f) }
|
modifier = remember { Modifier.weight(1f) }
|
||||||
@@ -187,6 +192,7 @@ private fun InnerReactionRow(
|
|||||||
ZapReaction(baseNote, MaterialTheme.colors.placeholderText, accountViewModel)
|
ZapReaction(baseNote, MaterialTheme.colors.placeholderText, accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
modifier = remember { Modifier.weight(1f) }
|
modifier = remember { Modifier.weight(1f) }
|
||||||
@@ -299,22 +305,44 @@ fun RenderZapRaiser(baseNote: Note, zapraiserAmount: Long, details: Boolean, acc
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExpandButton(baseNote: Note, content: @Composable () -> Unit) {
|
private fun WatchReactionsZapsBoostsAndDisplayIfExists(baseNote: Note, content: @Composable () -> Unit) {
|
||||||
val zapsState by baseNote.live().zaps.observeAsState()
|
val hasReactions by baseNote.live().zaps.combineWith(
|
||||||
val boostsState by baseNote.live().boosts.observeAsState()
|
liveData1 = baseNote.live().boosts,
|
||||||
val reactionsState by baseNote.live().reactions.observeAsState()
|
liveData2 = baseNote.live().reactions,
|
||||||
|
block = { zapsState, boostsState, reactionsState ->
|
||||||
val hasReactions by remember(zapsState, boostsState, reactionsState) {
|
zapsState?.note?.zaps?.isNotEmpty() == true ||
|
||||||
derivedStateOf {
|
boostsState?.note?.boosts?.isNotEmpty() == true ||
|
||||||
|
reactionsState?.note?.reactions?.isNotEmpty() == true
|
||||||
|
}
|
||||||
|
).observeAsState(
|
||||||
baseNote.zaps.isNotEmpty() ||
|
baseNote.zaps.isNotEmpty() ||
|
||||||
baseNote.boosts.isNotEmpty() ||
|
baseNote.boosts.isNotEmpty() ||
|
||||||
baseNote.reactions.isNotEmpty()
|
baseNote.reactions.isNotEmpty()
|
||||||
|
)
|
||||||
|
|
||||||
|
Crossfade(targetState = hasReactions) {
|
||||||
|
if (it) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasReactions) {
|
fun <T, K, P, R> LiveData<T>.combineWith(
|
||||||
content()
|
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
|
@Composable
|
||||||
@@ -325,7 +353,8 @@ private fun RenderShowIndividualReactionsButton(wantsToSeeReactions: MutableStat
|
|||||||
},
|
},
|
||||||
modifier = Size20Modifier
|
modifier = Size20Modifier
|
||||||
) {
|
) {
|
||||||
if (wantsToSeeReactions.value) {
|
Crossfade(targetState = wantsToSeeReactions.value) {
|
||||||
|
if (it) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.ExpandLess,
|
imageVector = Icons.Default.ExpandLess,
|
||||||
null,
|
null,
|
||||||
@@ -342,6 +371,7 @@ private fun RenderShowIndividualReactionsButton(wantsToSeeReactions: MutableStat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ReactionDetailGallery(
|
private fun ReactionDetailGallery(
|
||||||
@@ -503,7 +533,7 @@ fun ReplyReaction(
|
|||||||
fun ReplyCounter(baseNote: Note, textColor: Color) {
|
fun ReplyCounter(baseNote: Note, textColor: Color) {
|
||||||
val repliesState by baseNote.live().replies.map {
|
val repliesState by baseNote.live().replies.map {
|
||||||
it.note.replies.size
|
it.note.replies.size
|
||||||
}.observeAsState(0)
|
}.observeAsState(baseNote.replies.size)
|
||||||
|
|
||||||
SlidingAnimation(repliesState, textColor)
|
SlidingAnimation(repliesState, textColor)
|
||||||
}
|
}
|
||||||
@@ -521,6 +551,12 @@ private fun SlidingAnimation(baseCount: Int, textColor: Color) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { count ->
|
) { count ->
|
||||||
|
TextCount(count, textColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TextCount(count: Int, textColor: Color) {
|
||||||
Text(
|
Text(
|
||||||
text = remember(count) { showCount(count) },
|
text = remember(count) { showCount(count) },
|
||||||
fontSize = Font14SP,
|
fontSize = Font14SP,
|
||||||
@@ -529,7 +565,6 @@ private fun SlidingAnimation(baseCount: Int, textColor: Color) {
|
|||||||
maxLines = 1
|
maxLines = 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalAnimationApi::class)
|
@OptIn(ExperimentalAnimationApi::class)
|
||||||
@@ -634,7 +669,7 @@ fun BoostIcon(baseNote: Note, iconSize: Dp = Size20dp, grayTint: Color, accountV
|
|||||||
fun BoostText(baseNote: Note, grayTint: Color) {
|
fun BoostText(baseNote: Note, grayTint: Color) {
|
||||||
val boostState by baseNote.live().boosts.map {
|
val boostState by baseNote.live().boosts.map {
|
||||||
it.note.boosts.size
|
it.note.boosts.size
|
||||||
}.distinctUntilChanged().observeAsState(0)
|
}.distinctUntilChanged().observeAsState(baseNote.boosts.size)
|
||||||
|
|
||||||
SlidingAnimation(boostState, grayTint)
|
SlidingAnimation(boostState, grayTint)
|
||||||
}
|
}
|
||||||
@@ -786,7 +821,7 @@ fun LikeText(baseNote: Note, grayTint: Color) {
|
|||||||
val reactionsState by baseNote.live().reactions.observeAsState()
|
val reactionsState by baseNote.live().reactions.observeAsState()
|
||||||
|
|
||||||
var reactionsCount by remember(baseNote) {
|
var reactionsCount by remember(baseNote) {
|
||||||
mutableStateOf(0)
|
mutableStateOf(baseNote.reactions.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = reactionsState) {
|
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.LnZapEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.PayInvoiceErrorResponse
|
import com.vitorpamplona.amethyst.service.model.PayInvoiceErrorResponse
|
||||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
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.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
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 {
|
fun hasReactedTo(baseNote: Note, reaction: String): Boolean {
|
||||||
return account.hasReacted(baseNote, reaction)
|
return account.hasReacted(baseNote, reaction)
|
||||||
}
|
}
|
||||||
@@ -263,6 +271,29 @@ class AccountViewModel(val account: Account) : ViewModel() {
|
|||||||
return account.defaultZapType
|
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 {
|
class Factory(val account: Account) : ViewModelProvider.Factory {
|
||||||
override fun <AccountViewModel : ViewModel> create(modelClass: Class<AccountViewModel>): AccountViewModel {
|
override fun <AccountViewModel : ViewModel> create(modelClass: Class<AccountViewModel>): AccountViewModel {
|
||||||
return AccountViewModel(account) as 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 ReactionRowExpandButton = Modifier.width(65.dp).padding(start = 31.dp)
|
||||||
|
|
||||||
val WidthAuthorPictureModifier = Modifier.width(55.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 ReactionRowHeight = Modifier.height(24.dp).padding(start = 10.dp)
|
||||||
val ReactionRowHeightChat = Modifier.height(25.dp)
|
val ReactionRowHeightChat = Modifier.height(25.dp)
|
||||||
|
|||||||
Reference in New Issue
Block a user