diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt index 719dd6c61..afe48697c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt @@ -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 + } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index ee4822039..7d47aa240 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -159,7 +159,6 @@ data class RichTextViewerState( val customEmoji: ImmutableMap ) -@OptIn(ExperimentalLayoutApi::class) @Composable private fun RenderRegular( content: String, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt index c563d4264..03e4b4157 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt @@ -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) + } } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 61a63f647..36360fa2a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -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(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(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(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 + ) + } } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index 2e46aadaa..0736b34a2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -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 LiveData.combineWith( + liveData1: LiveData, + liveData2: LiveData

, + block: (T?, K?, P?) -> R +): LiveData { + val result = MediatorLiveData() + 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) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index d508e5d5a..20182dd47 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -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) -> 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 create(modelClass: Class): AccountViewModel { return AccountViewModel(account) as AccountViewModel diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt index 29bc4c2b8..e158a799a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt @@ -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)