- Merges Hidden and Reporting flows

- Removes Report Live data
- Refactors Full Bleed design
- Unifies Hidden and Report checks between the Video Feed, the Full Bleed Design and the Card layout.
This commit is contained in:
Vitor Pamplona
2024-06-12 16:54:39 -04:00
parent 0d00f2f80a
commit 45445c03e5
7 changed files with 432 additions and 574 deletions
@@ -258,7 +258,9 @@ open class Note(
if (repliesChanged) liveSet?.innerReplies?.invalidateData() if (repliesChanged) liveSet?.innerReplies?.invalidateData()
if (reactionsChanged) liveSet?.innerReactions?.invalidateData() if (reactionsChanged) liveSet?.innerReactions?.invalidateData()
if (boostsChanged) liveSet?.innerBoosts?.invalidateData() if (boostsChanged) liveSet?.innerBoosts?.invalidateData()
if (reportsChanged) liveSet?.innerReports?.invalidateData() if (reportsChanged) {
flowSet?.reports?.invalidateData()
}
if (zapsChanged) liveSet?.innerZaps?.invalidateData() if (zapsChanged) liveSet?.innerZaps?.invalidateData()
return toBeRemoved return toBeRemoved
@@ -290,7 +292,7 @@ open class Note(
if (reports[author]?.contains(deleteNote) == true) { if (reports[author]?.contains(deleteNote) == true) {
reports[author]?.let { reports[author]?.let {
reports = reports + Pair(author, it.minus(deleteNote)) reports = reports + Pair(author, it.minus(deleteNote))
liveSet?.innerReports?.invalidateData() flowSet?.reports?.invalidateData()
} }
} }
} }
@@ -399,10 +401,10 @@ open class Note(
if (reportsByAuthor == null) { if (reportsByAuthor == null) {
reports = reports + Pair(author, listOf(note)) reports = reports + Pair(author, listOf(note))
liveSet?.innerReports?.invalidateData() flowSet?.reports?.invalidateData()
} else if (!reportsByAuthor.contains(note)) { } else if (!reportsByAuthor.contains(note)) {
reports = reports + Pair(author, reportsByAuthor + note) reports = reports + Pair(author, reportsByAuthor + note)
liveSet?.innerReports?.invalidateData() flowSet?.reports?.invalidateData()
} }
} }
@@ -844,11 +846,13 @@ class NoteFlowSet(
) { ) {
// Observers line up here. // Observers line up here.
val metadata = NoteBundledRefresherFlow(u) val metadata = NoteBundledRefresherFlow(u)
val reports = NoteBundledRefresherFlow(u)
fun isInUse(): Boolean = metadata.stateFlow.subscriptionCount.value > 0 fun isInUse(): Boolean = metadata.stateFlow.subscriptionCount.value > 0 || reports.stateFlow.subscriptionCount.value > 0
fun destroy() { fun destroy() {
metadata.destroy() metadata.destroy()
reports.destroy()
} }
} }
@@ -861,7 +865,6 @@ class NoteLiveSet(
val innerReactions = NoteBundledRefresherLiveData(u) val innerReactions = NoteBundledRefresherLiveData(u)
val innerBoosts = NoteBundledRefresherLiveData(u) val innerBoosts = NoteBundledRefresherLiveData(u)
val innerReplies = NoteBundledRefresherLiveData(u) val innerReplies = NoteBundledRefresherLiveData(u)
val innerReports = NoteBundledRefresherLiveData(u)
val innerRelays = NoteBundledRefresherLiveData(u) val innerRelays = NoteBundledRefresherLiveData(u)
val innerZaps = NoteBundledRefresherLiveData(u) val innerZaps = NoteBundledRefresherLiveData(u)
val innerOts = NoteBundledRefresherLiveData(u) val innerOts = NoteBundledRefresherLiveData(u)
@@ -871,7 +874,6 @@ class NoteLiveSet(
val reactions = innerReactions.map { it } val reactions = innerReactions.map { it }
val boosts = innerBoosts.map { it } val boosts = innerBoosts.map { it }
val replies = innerReplies.map { it } val replies = innerReplies.map { it }
val reports = innerReports.map { it }
val relays = innerRelays.map { it } val relays = innerRelays.map { it }
val zaps = innerZaps.map { it } val zaps = innerZaps.map { it }
@@ -907,7 +909,6 @@ class NoteLiveSet(
reactions.hasObservers() || reactions.hasObservers() ||
boosts.hasObservers() || boosts.hasObservers() ||
replies.hasObservers() || replies.hasObservers() ||
reports.hasObservers() ||
relays.hasObservers() || relays.hasObservers() ||
zaps.hasObservers() || zaps.hasObservers() ||
hasEvent.hasObservers() || hasEvent.hasObservers() ||
@@ -923,7 +924,6 @@ class NoteLiveSet(
innerReactions.destroy() innerReactions.destroy()
innerBoosts.destroy() innerBoosts.destroy()
innerReplies.destroy() innerReplies.destroy()
innerReports.destroy()
innerRelays.destroy() innerRelays.destroy()
innerZaps.destroy() innerZaps.destroy()
innerOts.destroy() innerOts.destroy()
@@ -22,9 +22,7 @@ package com.vitorpamplona.amethyst.ui.note
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@@ -38,12 +36,12 @@ fun CheckHiddenFeedWatchBlockAndReport(
note: Note, note: Note,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
showHiddenWarning: Boolean, showHiddenWarning: Boolean,
showHidden: Boolean = false, ignoreAllBlocksAndReports: Boolean = false,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
normalNote: @Composable (canPreview: Boolean) -> Unit, normalNote: @Composable (canPreview: Boolean) -> Unit,
) { ) {
if (showHidden) { if (ignoreAllBlocksAndReports) {
// Ignores reports as well // Ignores reports as well
normalNote(true) normalNote(true)
} else { } else {
@@ -62,19 +60,28 @@ fun WatchBlockAndReport(
nav: (String) -> Unit, nav: (String) -> Unit,
normalNote: @Composable (canPreview: Boolean) -> Unit, normalNote: @Composable (canPreview: Boolean) -> Unit,
) { ) {
val isHiddenState by accountViewModel.createIsHiddenFlow(note).collectAsStateWithLifecycle() val hiddenState by accountViewModel.createIsHiddenFlow(note).collectAsStateWithLifecycle()
val showAnyway = val showAnyway =
remember { remember {
mutableStateOf(false) mutableStateOf(false)
} }
Crossfade(targetState = isHiddenState, label = "CheckHiddenNoteCompose") { isHidden -> Crossfade(targetState = hiddenState, label = "CheckHiddenNoteCompose") { isHidden ->
if (showAnyway.value) { if (showAnyway.value) {
normalNote(true) normalNote(true)
} else if (!isHidden) { } else if (!isHidden.isPostHidden) {
LoadReportsNoteCompose(note, modifier, accountViewModel, nav) { canPreview -> if (isHidden.isAcceptable) {
normalNote(canPreview) normalNote(isHidden.canPreview)
} else {
HiddenNote(
isHidden.relevantReports,
isHidden.isHiddenAuthor,
accountViewModel,
modifier,
nav,
onClick = { showAnyway.value = true },
)
} }
} else if (showHiddenWarning) { } else if (showHiddenWarning) {
// if it is a quoted or boosted note, how the hidden warning. // if it is a quoted or boosted note, how the hidden warning.
@@ -84,75 +91,3 @@ fun WatchBlockAndReport(
} }
} }
} }
@Composable
private fun LoadReportsNoteCompose(
note: Note,
modifier: Modifier = Modifier,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
normalNote: @Composable (canPreview: Boolean) -> Unit,
) {
var state by
remember(note) {
mutableStateOf(
AccountViewModel.NoteComposeReportState(),
)
}
WatchForReports(note, accountViewModel) { newState ->
if (state != newState) {
state = newState
}
}
Crossfade(targetState = state, label = "LoadedNoteCompose") {
RenderReportState(state = it, note = note, modifier = modifier, accountViewModel = accountViewModel, nav = nav) { canPreview ->
normalNote(canPreview)
}
}
}
@Composable
private fun RenderReportState(
state: AccountViewModel.NoteComposeReportState,
note: Note,
modifier: Modifier = Modifier,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
normalNote: @Composable (canPreview: Boolean) -> Unit,
) {
var showReportedNote by remember(note) { mutableStateOf(false) }
Crossfade(targetState = !state.isAcceptable && !showReportedNote, label = "RenderReportState") { showHiddenNote ->
if (showHiddenNote) {
HiddenNote(
state.relevantReports,
state.isHiddenAuthor,
accountViewModel,
modifier,
nav,
onClick = { showReportedNote = true },
)
} else {
val canPreview = (!state.isAcceptable && showReportedNote) || state.canPreview
normalNote(canPreview)
}
}
}
@Composable
fun WatchForReports(
note: Note,
accountViewModel: AccountViewModel,
onChange: (AccountViewModel.NoteComposeReportState) -> Unit,
) {
val userFollowsState by accountViewModel.userFollows.observeAsState()
val noteReportsState by note.live().reports.observeAsState()
val userBlocks by accountViewModel.account.flowHiddenUsers.collectAsStateWithLifecycle()
LaunchedEffect(key1 = noteReportsState, key2 = userFollowsState, userBlocks) {
accountViewModel.isNoteAcceptable(note, onChange)
}
}
@@ -49,7 +49,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.BottomStart import androidx.compose.ui.Alignment.Companion.BottomStart
import androidx.compose.ui.Alignment.Companion.Center
import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Alignment.Companion.TopEnd import androidx.compose.ui.Alignment.Companion.TopEnd
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -124,7 +123,7 @@ fun ChannelCardCompose(
CheckHiddenFeedWatchBlockAndReport( CheckHiddenFeedWatchBlockAndReport(
note = baseNote, note = baseNote,
modifier = modifier, modifier = modifier,
showHidden = isHiddenFeed, ignoreAllBlocksAndReports = isHiddenFeed,
showHiddenWarning = false, showHiddenWarning = false,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav, nav = nav,
@@ -308,8 +307,7 @@ fun RenderClassifiedsThumb(
title = noteEvent?.title(), title = noteEvent?.title(),
price = noteEvent?.price(), price = noteEvent?.price(),
) )
} }.distinctUntilChanged()
.distinctUntilChanged()
.observeAsState( .observeAsState(
ClassifiedsThumb( ClassifiedsThumb(
image = noteEvent.image(), image = noteEvent.image(),
@@ -437,8 +435,7 @@ fun RenderLiveActivityThumb(
status = noteEvent?.status(), status = noteEvent?.status(),
starts = noteEvent?.starts(), starts = noteEvent?.starts(),
) )
} }.distinctUntilChanged()
.distinctUntilChanged()
.observeAsState( .observeAsState(
LiveActivityCard( LiveActivityCard(
name = noteEvent.dTag(), name = noteEvent.dTag(),
@@ -567,8 +564,7 @@ fun RenderCommunitiesThumb(
cover = noteEvent?.image()?.ifBlank { null }, cover = noteEvent?.image()?.ifBlank { null },
moderators = noteEvent?.moderators()?.toImmutableList() ?: persistentListOf(), moderators = noteEvent?.moderators()?.toImmutableList() ?: persistentListOf(),
) )
} }.distinctUntilChanged()
.distinctUntilChanged()
.observeAsState( .observeAsState(
CommunityCard( CommunityCard(
name = noteEvent.dTag(), name = noteEvent.dTag(),
@@ -672,7 +668,9 @@ fun LoadModerators(
} }
} }
val followingKeySet = accountViewModel.account.liveDiscoveryFollowLists.value?.users val followingKeySet =
accountViewModel.account.liveDiscoveryFollowLists.value
?.users
val allParticipants = val allParticipants =
ParticipantListBuilder().followsThatParticipateOn(baseNote, followingKeySet).minus(hosts) ParticipantListBuilder().followsThatParticipateOn(baseNote, followingKeySet).minus(hosts)
@@ -723,7 +721,9 @@ private fun LoadParticipants(
val hostsAuthor = hosts + (baseNote.author?.let { listOf(it) } ?: emptyList<User>()) val hostsAuthor = hosts + (baseNote.author?.let { listOf(it) } ?: emptyList<User>())
val followingKeySet = accountViewModel.account.liveDiscoveryFollowLists.value?.users val followingKeySet =
accountViewModel.account.liveDiscoveryFollowLists.value
?.users
val allParticipants = val allParticipants =
ParticipantListBuilder() ParticipantListBuilder()
@@ -760,7 +760,11 @@ fun RenderContentDVMThumb(
nav: (String) -> Unit, nav: (String) -> Unit,
) { ) {
// downloads user metadata to pre-load the NIP-65 relays. // downloads user metadata to pre-load the NIP-65 relays.
val user = baseNote.author?.live()?.metadata?.observeAsState() val user =
baseNote.author
?.live()
?.metadata
?.observeAsState()
val card = observeAppDefinition(appDefinitionNote = baseNote) val card = observeAppDefinition(appDefinitionNote = baseNote)
@@ -875,7 +879,9 @@ fun RenderChannelThumb(
LaunchedEffect(key1 = channelUpdates) { LaunchedEffect(key1 = channelUpdates) {
launch(Dispatchers.IO) { launch(Dispatchers.IO) {
val followingKeySet = accountViewModel.account.liveDiscoveryFollowLists.value?.users val followingKeySet =
accountViewModel.account.liveDiscoveryFollowLists.value
?.users
val allParticipants = val allParticipants =
ParticipantListBuilder() ParticipantListBuilder()
.followsThatParticipateOn(baseNote, followingKeySet) .followsThatParticipateOn(baseNote, followingKeySet)
@@ -204,7 +204,7 @@ fun NoteCompose(
CheckHiddenFeedWatchBlockAndReport( CheckHiddenFeedWatchBlockAndReport(
note = baseNote, note = baseNote,
modifier = modifier, modifier = modifier,
showHidden = isHiddenFeed, ignoreAllBlocksAndReports = isHiddenFeed,
showHiddenWarning = isQuotedNote || isBoostedNote, showHiddenWarning = isQuotedNote || isBoostedNote,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav, nav = nav,
@@ -264,9 +264,7 @@ fun AcceptableNote(
} }
is BadgeDefinitionEvent -> BadgeDisplay(baseNote = baseNote) is BadgeDefinitionEvent -> BadgeDisplay(baseNote = baseNote)
else -> else ->
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup ->
showPopup,
->
CheckNewAndRenderNote( CheckNewAndRenderNote(
baseNote = baseNote, baseNote = baseNote,
routeForLastRead = routeForLastRead, routeForLastRead = routeForLastRead,
@@ -36,11 +36,8 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.ProvideTextStyle
@@ -49,9 +46,9 @@ import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@@ -62,7 +59,6 @@ import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
@@ -85,19 +81,21 @@ import com.vitorpamplona.amethyst.ui.components.InlineCarrousel
import com.vitorpamplona.amethyst.ui.components.LoadNote import com.vitorpamplona.amethyst.ui.components.LoadNote
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
import com.vitorpamplona.amethyst.ui.components.mockAccountViewModel import com.vitorpamplona.amethyst.ui.components.mockAccountViewModel
import com.vitorpamplona.amethyst.ui.navigation.routeFor
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
import com.vitorpamplona.amethyst.ui.note.BlankNote import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport
import com.vitorpamplona.amethyst.ui.note.DisplayDraft import com.vitorpamplona.amethyst.ui.note.DisplayDraft
import com.vitorpamplona.amethyst.ui.note.DisplayOtsIfInOriginal import com.vitorpamplona.amethyst.ui.note.DisplayOtsIfInOriginal
import com.vitorpamplona.amethyst.ui.note.HiddenNote
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
import com.vitorpamplona.amethyst.ui.note.LongPressToQuickAction
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.note.NoteQuickActionMenu
import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay
import com.vitorpamplona.amethyst.ui.note.ReactionsRow import com.vitorpamplona.amethyst.ui.note.ReactionsRow
import com.vitorpamplona.amethyst.ui.note.RenderDraft import com.vitorpamplona.amethyst.ui.note.RenderDraft
import com.vitorpamplona.amethyst.ui.note.RenderRepost import com.vitorpamplona.amethyst.ui.note.RenderRepost
import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent
import com.vitorpamplona.amethyst.ui.note.calculateBackgroundColor
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader
import com.vitorpamplona.amethyst.ui.note.elements.DisplayEditStatus import com.vitorpamplona.amethyst.ui.note.elements.DisplayEditStatus
import com.vitorpamplona.amethyst.ui.note.elements.DisplayFollowingCommunityInPost import com.vitorpamplona.amethyst.ui.note.elements.DisplayFollowingCommunityInPost
@@ -107,15 +105,14 @@ import com.vitorpamplona.amethyst.ui.note.elements.DisplayPoW
import com.vitorpamplona.amethyst.ui.note.elements.DisplayReward import com.vitorpamplona.amethyst.ui.note.elements.DisplayReward
import com.vitorpamplona.amethyst.ui.note.elements.DisplayZapSplits import com.vitorpamplona.amethyst.ui.note.elements.DisplayZapSplits
import com.vitorpamplona.amethyst.ui.note.elements.ForkInformationRow import com.vitorpamplona.amethyst.ui.note.elements.ForkInformationRow
import com.vitorpamplona.amethyst.ui.note.elements.NoteDropDownMenu import com.vitorpamplona.amethyst.ui.note.elements.MoreOptionsButton
import com.vitorpamplona.amethyst.ui.note.elements.Reward import com.vitorpamplona.amethyst.ui.note.elements.Reward
import com.vitorpamplona.amethyst.ui.note.elements.TimeAgo
import com.vitorpamplona.amethyst.ui.note.observeEdits import com.vitorpamplona.amethyst.ui.note.observeEdits
import com.vitorpamplona.amethyst.ui.note.showAmount import com.vitorpamplona.amethyst.ui.note.showAmount
import com.vitorpamplona.amethyst.ui.note.timeAgo
import com.vitorpamplona.amethyst.ui.note.types.AudioHeader import com.vitorpamplona.amethyst.ui.note.types.AudioHeader
import com.vitorpamplona.amethyst.ui.note.types.AudioTrackHeader import com.vitorpamplona.amethyst.ui.note.types.AudioTrackHeader
import com.vitorpamplona.amethyst.ui.note.types.BadgeDisplay import com.vitorpamplona.amethyst.ui.note.types.BadgeDisplay
import com.vitorpamplona.amethyst.ui.note.types.DisplayHighlight
import com.vitorpamplona.amethyst.ui.note.types.DisplayPeopleList import com.vitorpamplona.amethyst.ui.note.types.DisplayPeopleList
import com.vitorpamplona.amethyst.ui.note.types.DisplayRelaySet import com.vitorpamplona.amethyst.ui.note.types.DisplayRelaySet
import com.vitorpamplona.amethyst.ui.note.types.EditState import com.vitorpamplona.amethyst.ui.note.types.EditState
@@ -128,6 +125,7 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderFhirResource
import com.vitorpamplona.amethyst.ui.note.types.RenderGitIssueEvent import com.vitorpamplona.amethyst.ui.note.types.RenderGitIssueEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderGitPatchEvent import com.vitorpamplona.amethyst.ui.note.types.RenderGitPatchEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderGitRepositoryEvent import com.vitorpamplona.amethyst.ui.note.types.RenderGitRepositoryEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderHighlight
import com.vitorpamplona.amethyst.ui.note.types.RenderLiveActivityChatMessage import com.vitorpamplona.amethyst.ui.note.types.RenderLiveActivityChatMessage
import com.vitorpamplona.amethyst.ui.note.types.RenderPinListEvent import com.vitorpamplona.amethyst.ui.note.types.RenderPinListEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderPoll import com.vitorpamplona.amethyst.ui.note.types.RenderPoll
@@ -144,8 +142,7 @@ import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder
import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier
import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier import com.vitorpamplona.amethyst.ui.theme.Size55dp
import com.vitorpamplona.amethyst.ui.theme.Size24Modifier
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
@@ -184,7 +181,6 @@ import com.vitorpamplona.quartz.events.TextNoteModificationEvent
import com.vitorpamplona.quartz.events.VideoEvent import com.vitorpamplona.quartz.events.VideoEvent
import com.vitorpamplona.quartz.events.WikiNoteEvent import com.vitorpamplona.quartz.events.WikiNoteEvent
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.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -225,7 +221,10 @@ fun RenderThreadFeed(
LaunchedEffect(noteId) { LaunchedEffect(noteId) {
// waits to load the thread to scroll to item. // waits to load the thread to scroll to item.
delay(100) delay(100)
val noteForPosition = state.feed.value.filter { it.idHex == noteId }.firstOrNull() val noteForPosition =
state.feed.value
.filter { it.idHex == noteId }
.firstOrNull()
var position = state.feed.value.indexOf(noteForPosition) var position = state.feed.value.indexOf(noteForPosition)
if (position >= 0) { if (position >= 0) {
@@ -321,347 +320,314 @@ fun Modifier.drawReplyLevel(
} }
return@drawBehind return@drawBehind
} }.padding(start = (2 + (level * 3)).dp)
.padding(start = (2 + (level * 3)).dp)
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun NoteMaster( fun NoteMaster(
baseNote: Note, baseNote: Note,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
parentBackgroundColor: MutableState<Color>? = null,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: (String) -> Unit, nav: (String) -> Unit,
) { ) {
val noteState by baseNote.live().metadata.observeAsState() WatchNoteEvent(
val note = noteState?.note baseNote = baseNote,
accountViewModel = accountViewModel,
val noteReportsState by baseNote.live().reports.observeAsState() modifier,
val noteForReports = noteReportsState?.note ?: return ) {
CheckHiddenFeedWatchBlockAndReport(
val accountState by accountViewModel.accountLiveData.observeAsState() note = baseNote,
val account = accountState?.account ?: return modifier = modifier,
ignoreAllBlocksAndReports = false,
var showHiddenNote by remember { mutableStateOf(false) } showHiddenWarning = true,
val context = LocalContext.current
val moreActionsExpanded = remember { mutableStateOf(false) }
val enablePopup = remember { { moreActionsExpanded.value = true } }
val noteEvent = note?.event
var popupExpanded by remember { mutableStateOf(false) }
val defaultBackgroundColor = MaterialTheme.colorScheme.background
val backgroundColor = remember { mutableStateOf<Color>(defaultBackgroundColor) }
if (noteEvent == null) {
BlankNote()
} else if (!account.isAcceptable(noteForReports) && !showHiddenNote) {
val reports = remember { account.getRelevantReports(noteForReports).toImmutableSet() }
HiddenNote(
reports,
note.author?.let { account.isHidden(it) } ?: false,
accountViewModel,
Modifier.fillMaxWidth(),
nav,
onClick = { showHiddenNote = true },
)
} else {
Column(
modifier
.fillMaxWidth()
.padding(top = 10.dp),
) {
val editState = observeEdits(baseNote = baseNote, accountViewModel = accountViewModel)
Row(
modifier =
Modifier
.padding(start = 12.dp, end = 12.dp)
.clickable(onClick = { note.author?.let { nav("User/${it.pubkeyHex}") } }),
) {
NoteAuthorPicture(
baseNote = baseNote,
nav = nav,
accountViewModel = accountViewModel,
size = 55.dp,
)
Column(modifier = Modifier.padding(start = 10.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
NoteUsernameDisplay(baseNote, Modifier.weight(1f))
val isCommunityPost by
remember(baseNote) {
derivedStateOf {
baseNote.event?.isTaggedAddressableKind(CommunityDefinitionEvent.KIND) == true
}
}
if (isCommunityPost) {
DisplayFollowingCommunityInPost(baseNote, accountViewModel, nav)
} else {
DisplayFollowingHashtagsInPost(baseNote, accountViewModel, nav)
}
if (editState.value is GenericLoadable.Loaded) {
(editState.value as? GenericLoadable.Loaded<EditState>)?.loaded?.let {
DisplayEditStatus(it)
}
}
Text(
timeAgo(note.createdAt(), context = context),
color = MaterialTheme.colorScheme.placeholderText,
maxLines = 1,
)
IconButton(
modifier = Size24Modifier,
onClick = enablePopup,
) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = stringResource(id = R.string.more_options),
modifier = Size15Modifier,
tint = MaterialTheme.colorScheme.placeholderText,
)
NoteDropDownMenu(baseNote, moreActionsExpanded, editState, accountViewModel, nav)
}
}
Row(verticalAlignment = Alignment.CenterVertically) {
ObserveDisplayNip05Status(
baseNote,
remember { Modifier.weight(1f) },
accountViewModel,
nav,
)
val geo = remember { noteEvent.getGeoHash() }
if (geo != null) {
DisplayLocation(geo, nav)
}
val baseReward = remember { noteEvent.getReward()?.let { Reward(it) } }
if (baseReward != null) {
DisplayReward(baseReward, baseNote, accountViewModel, nav)
}
val pow = remember { noteEvent.getPoWRank() }
if (pow > 20) {
DisplayPoW(pow)
}
if (note.isDraft()) {
DisplayDraft()
}
DisplayOtsIfInOriginal(note, editState, accountViewModel)
}
}
}
Spacer(modifier = Modifier.height(10.dp))
if (noteEvent is BadgeDefinitionEvent) {
BadgeDisplay(baseNote = note)
} else if (noteEvent is LongTextNoteEvent) {
RenderLongFormHeaderForThread(noteEvent)
} else if (noteEvent is WikiNoteEvent) {
RenderWikiHeaderForThread(noteEvent, accountViewModel, nav)
} else if (noteEvent is ClassifiedsEvent) {
RenderClassifiedsReaderForThread(noteEvent, note, accountViewModel, nav)
}
Row(
modifier =
Modifier
.padding(horizontal = 12.dp)
.combinedClickable(
onClick = {},
onLongClick = { popupExpanded = true },
),
) {
Column {
val canPreview =
note.author == account.userProfile() ||
(note.author?.let { account.userProfile().isFollowingCached(it) } ?: true) ||
!noteForReports.hasAnyReports()
if (
(noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) &&
note.channelHex() != null
) {
ChannelHeader(
channelHex = note.channelHex()!!,
showVideo = true,
sendToChannel = true,
accountViewModel = accountViewModel,
nav = nav,
)
} else if (noteEvent is VideoEvent) {
VideoDisplay(baseNote, false, true, backgroundColor, false, accountViewModel, nav)
} else if (noteEvent is FileHeaderEvent) {
FileHeaderDisplay(baseNote, true, false, accountViewModel)
} else if (noteEvent is FileStorageHeaderEvent) {
FileStorageHeaderDisplay(baseNote, true, false, accountViewModel)
} else if (noteEvent is PeopleListEvent) {
DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
} else if (noteEvent is AudioTrackEvent) {
AudioTrackHeader(noteEvent, baseNote, false, accountViewModel, nav)
} else if (noteEvent is AudioHeaderEvent) {
AudioHeader(noteEvent, baseNote, false, accountViewModel, nav)
} else if (noteEvent is CommunityPostApprovalEvent) {
RenderPostApproval(
baseNote,
quotesLeft = 3,
backgroundColor,
accountViewModel,
nav,
)
} else if (noteEvent is PinListEvent) {
RenderPinListEvent(
baseNote,
backgroundColor,
accountViewModel,
nav,
)
} else if (noteEvent is EmojiPackEvent) {
RenderEmojiPack(
baseNote,
true,
backgroundColor,
accountViewModel,
)
} else if (noteEvent is RelaySetEvent) {
DisplayRelaySet(
baseNote,
backgroundColor,
accountViewModel,
nav,
)
} else if (noteEvent is FhirResourceEvent) {
RenderFhirResource(baseNote, accountViewModel, nav)
} else if (noteEvent is GitRepositoryEvent) {
RenderGitRepositoryEvent(baseNote, accountViewModel, nav)
} else if (noteEvent is GitPatchEvent) {
RenderGitPatchEvent(baseNote, false, true, quotesLeft = 3, backgroundColor, accountViewModel, nav)
} else if (noteEvent is GitIssueEvent) {
RenderGitIssueEvent(baseNote, false, true, quotesLeft = 3, backgroundColor, accountViewModel, nav)
} else if (noteEvent is AppDefinitionEvent) {
RenderAppDefinition(baseNote, accountViewModel, nav)
} else if (noteEvent is DraftEvent) {
RenderDraft(baseNote, 3, true, backgroundColor, accountViewModel, nav)
} else if (noteEvent is HighlightEvent) {
DisplayHighlight(
noteEvent.quote(),
noteEvent.author(),
noteEvent.inUrl(),
noteEvent.inPost(),
false,
true,
quotesLeft = 3,
backgroundColor,
accountViewModel,
nav,
)
} else if (noteEvent is RepostEvent || noteEvent is GenericRepostEvent) {
RenderRepost(baseNote, quotesLeft = 3, backgroundColor, accountViewModel, nav)
} else if (noteEvent is TextNoteModificationEvent) {
RenderTextModificationEvent(
note = baseNote,
makeItShort = false,
canPreview = true,
quotesLeft = 3,
backgroundColor,
accountViewModel,
nav,
)
} else if (noteEvent is PollNoteEvent) {
RenderPoll(
baseNote,
false,
canPreview,
quotesLeft = 3,
unPackReply = false,
backgroundColor,
accountViewModel,
nav,
)
} else if (noteEvent is PrivateDmEvent) {
RenderPrivateMessage(
baseNote,
false,
canPreview,
3,
backgroundColor,
accountViewModel,
nav,
)
} else if (noteEvent is ChannelMessageEvent) {
RenderChannelMessage(
baseNote,
false,
canPreview,
3,
backgroundColor,
editState,
accountViewModel,
nav,
)
} else if (noteEvent is LiveActivitiesChatMessageEvent) {
RenderLiveActivityChatMessage(
baseNote,
false,
canPreview,
3,
backgroundColor,
editState,
accountViewModel,
nav,
)
} else {
RenderTextEvent(
baseNote,
false,
canPreview,
quotesLeft = 3,
unPackReply = false,
backgroundColor,
editState,
accountViewModel,
nav,
)
}
}
}
val noteEvent = baseNote.event
val zapSplits = remember(noteEvent) { noteEvent?.hasZapSplitSetup() ?: false }
if (zapSplits && noteEvent != null) {
Spacer(modifier = DoubleVertSpacer)
Row(
modifier = Modifier.padding(horizontal = 12.dp),
) {
DisplayZapSplits(noteEvent, false, accountViewModel, nav)
}
}
ReactionsRow(note, true, true, editState, accountViewModel, nav)
}
NoteQuickActionMenu(
note = note,
popupExpanded = popupExpanded,
onDismiss = { popupExpanded = false },
onWantsToEditDraft = { },
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
nav = nav, nav = nav,
) { canPreview ->
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup ->
FullBleedNoteCompose(
baseNote,
modifier
.combinedClickable(
onClick = {},
onLongClick = showPopup,
),
canPreview,
parentBackgroundColor = parentBackgroundColor,
accountViewModel,
nav,
)
}
}
}
}
@Composable
private fun FullBleedNoteCompose(
baseNote: Note,
modifier: Modifier,
canPreview: Boolean,
parentBackgroundColor: MutableState<Color>?,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val noteEvent = baseNote.event ?: return
val backgroundColor =
calculateBackgroundColor(
baseNote.createdAt(),
null,
parentBackgroundColor,
accountViewModel,
) )
Column(
Modifier
.fillMaxWidth()
.padding(top = 10.dp),
) {
val editState = observeEdits(baseNote = baseNote, accountViewModel = accountViewModel)
Row(
modifier =
Modifier
.padding(start = 12.dp, end = 12.dp)
.clickable(onClick = { baseNote.author?.let { nav(routeFor(it)) } }),
) {
NoteAuthorPicture(
baseNote = baseNote,
nav = nav,
accountViewModel = accountViewModel,
size = Size55dp,
)
Column(modifier = Modifier.padding(start = 10.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
NoteUsernameDisplay(baseNote, Modifier.weight(1f))
val isCommunityPost by
remember(baseNote) {
derivedStateOf {
baseNote.event?.isTaggedAddressableKind(CommunityDefinitionEvent.KIND) == true
}
}
if (isCommunityPost) {
DisplayFollowingCommunityInPost(baseNote, accountViewModel, nav)
} else {
DisplayFollowingHashtagsInPost(baseNote, accountViewModel, nav)
}
if (editState.value is GenericLoadable.Loaded) {
(editState.value as? GenericLoadable.Loaded<EditState>)?.loaded?.let {
DisplayEditStatus(it)
}
}
TimeAgo(note = baseNote)
MoreOptionsButton(baseNote, editState, accountViewModel, nav)
}
Row(verticalAlignment = Alignment.CenterVertically) {
ObserveDisplayNip05Status(
baseNote,
remember { Modifier.weight(1f) },
accountViewModel,
nav,
)
val geo = remember { noteEvent.getGeoHash() }
if (geo != null) {
DisplayLocation(geo, nav)
}
val baseReward = remember { noteEvent.getReward()?.let { Reward(it) } }
if (baseReward != null) {
DisplayReward(baseReward, baseNote, accountViewModel, nav)
}
val pow = remember { noteEvent.getPoWRank() }
if (pow > 20) {
DisplayPoW(pow)
}
if (baseNote.isDraft()) {
DisplayDraft()
}
DisplayOtsIfInOriginal(baseNote, editState, accountViewModel)
}
}
}
Spacer(modifier = Modifier.height(10.dp))
if (noteEvent is BadgeDefinitionEvent) {
BadgeDisplay(baseNote = baseNote)
} else if (noteEvent is LongTextNoteEvent) {
RenderLongFormHeaderForThread(noteEvent)
} else if (noteEvent is WikiNoteEvent) {
RenderWikiHeaderForThread(noteEvent, accountViewModel, nav)
} else if (noteEvent is ClassifiedsEvent) {
RenderClassifiedsReaderForThread(noteEvent, baseNote, accountViewModel, nav)
}
Row(
modifier =
modifier
.padding(horizontal = 12.dp),
) {
Column {
if (
(noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) &&
baseNote.channelHex() != null
) {
ChannelHeader(
channelHex = baseNote.channelHex()!!,
showVideo = true,
sendToChannel = true,
accountViewModel = accountViewModel,
nav = nav,
)
} else if (noteEvent is VideoEvent) {
VideoDisplay(baseNote, false, true, backgroundColor, false, accountViewModel, nav)
} else if (noteEvent is FileHeaderEvent) {
FileHeaderDisplay(baseNote, true, false, accountViewModel)
} else if (noteEvent is FileStorageHeaderEvent) {
FileStorageHeaderDisplay(baseNote, true, false, accountViewModel)
} else if (noteEvent is PeopleListEvent) {
DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
} else if (noteEvent is AudioTrackEvent) {
AudioTrackHeader(noteEvent, baseNote, false, accountViewModel, nav)
} else if (noteEvent is AudioHeaderEvent) {
AudioHeader(noteEvent, baseNote, false, accountViewModel, nav)
} else if (noteEvent is CommunityPostApprovalEvent) {
RenderPostApproval(
baseNote,
quotesLeft = 3,
backgroundColor,
accountViewModel,
nav,
)
} else if (noteEvent is PinListEvent) {
RenderPinListEvent(
baseNote,
backgroundColor,
accountViewModel,
nav,
)
} else if (noteEvent is EmojiPackEvent) {
RenderEmojiPack(
baseNote,
true,
backgroundColor,
accountViewModel,
)
} else if (noteEvent is RelaySetEvent) {
DisplayRelaySet(
baseNote,
backgroundColor,
accountViewModel,
nav,
)
} else if (noteEvent is FhirResourceEvent) {
RenderFhirResource(baseNote, accountViewModel, nav)
} else if (noteEvent is GitRepositoryEvent) {
RenderGitRepositoryEvent(baseNote, accountViewModel, nav)
} else if (noteEvent is GitPatchEvent) {
RenderGitPatchEvent(baseNote, false, true, quotesLeft = 3, backgroundColor, accountViewModel, nav)
} else if (noteEvent is GitIssueEvent) {
RenderGitIssueEvent(baseNote, false, true, quotesLeft = 3, backgroundColor, accountViewModel, nav)
} else if (noteEvent is AppDefinitionEvent) {
RenderAppDefinition(baseNote, accountViewModel, nav)
} else if (noteEvent is DraftEvent) {
RenderDraft(baseNote, 3, true, backgroundColor, accountViewModel, nav)
} else if (noteEvent is HighlightEvent) {
RenderHighlight(baseNote, false, canPreview, quotesLeft = 3, backgroundColor, accountViewModel, nav)
} else if (noteEvent is RepostEvent || noteEvent is GenericRepostEvent) {
RenderRepost(baseNote, quotesLeft = 3, backgroundColor, accountViewModel, nav)
} else if (noteEvent is TextNoteModificationEvent) {
RenderTextModificationEvent(
note = baseNote,
makeItShort = false,
canPreview = true,
quotesLeft = 3,
backgroundColor,
accountViewModel,
nav,
)
} else if (noteEvent is PollNoteEvent) {
RenderPoll(
baseNote,
false,
canPreview,
quotesLeft = 3,
unPackReply = false,
backgroundColor,
accountViewModel,
nav,
)
} else if (noteEvent is PrivateDmEvent) {
RenderPrivateMessage(
baseNote,
false,
canPreview,
3,
backgroundColor,
accountViewModel,
nav,
)
} else if (noteEvent is ChannelMessageEvent) {
RenderChannelMessage(
baseNote,
false,
canPreview,
3,
backgroundColor,
editState,
accountViewModel,
nav,
)
} else if (noteEvent is LiveActivitiesChatMessageEvent) {
RenderLiveActivityChatMessage(
baseNote,
false,
canPreview,
3,
backgroundColor,
editState,
accountViewModel,
nav,
)
} else {
RenderTextEvent(
baseNote,
false,
canPreview,
quotesLeft = 3,
unPackReply = false,
backgroundColor,
editState,
accountViewModel,
nav,
)
}
}
}
val noteEvent = baseNote.event
val zapSplits = remember(noteEvent) { noteEvent?.hasZapSplitSetup() ?: false }
if (zapSplits && noteEvent != null) {
Spacer(modifier = DoubleVertSpacer)
Row(
modifier = Modifier.padding(horizontal = 12.dp),
) {
DisplayZapSplits(noteEvent, false, accountViewModel, nav)
}
}
ReactionsRow(baseNote, true, true, editState, accountViewModel, nav)
} }
} }
@@ -254,16 +254,70 @@ class AccountViewModel(
} }
} }
val noteIsHiddenFlows = LruCache<Note, StateFlow<Boolean>>(300) @Immutable
data class NoteComposeReportState(
val isPostHidden: Boolean = false,
val isAcceptable: Boolean = true,
val canPreview: Boolean = true,
val isHiddenAuthor: Boolean = false,
val relevantReports: ImmutableSet<Note> = persistentSetOf(),
)
fun createIsHiddenFlow(note: Note): StateFlow<Boolean> = fun isNoteAcceptable(
note: Note,
accountChoices: Account.LiveHiddenUsers,
followUsers: Set<HexKey>,
): NoteComposeReportState {
val isFromLoggedIn = note.author?.pubkeyHex == userProfile().pubkeyHex
val isFromLoggedInFollow = note.author?.let { followUsers.contains(it.pubkeyHex) } ?: true
val isPostHidden = note.isHiddenFor(accountChoices)
val isHiddenAuthor = note.author?.let { account.isHidden(it) } == true
return if (isPostHidden) {
// Spam + Blocked Users + Hidden Words + Sensitive Content
NoteComposeReportState(isPostHidden, false, false, isHiddenAuthor, persistentSetOf())
} else if (isFromLoggedIn || isFromLoggedInFollow) {
// No need to process if from trusted people
NoteComposeReportState(isPostHidden, true, true, isHiddenAuthor, persistentSetOf())
} else {
val newCanPreview = !note.hasAnyReports()
val newIsAcceptable = account.isAcceptable(note)
if (newCanPreview && newIsAcceptable) {
// No need to process reports if nothing is wrong
NoteComposeReportState(isPostHidden, true, true, false, persistentSetOf())
} else {
val newRelevantReports = account.getRelevantReports(note)
NoteComposeReportState(
isPostHidden,
newIsAcceptable,
newCanPreview,
false,
newRelevantReports.toImmutableSet(),
)
}
}
}
val noteIsHiddenFlows = LruCache<Note, StateFlow<NoteComposeReportState>>(300)
fun createIsHiddenFlow(note: Note): StateFlow<NoteComposeReportState> =
noteIsHiddenFlows.get(note) noteIsHiddenFlows.get(note)
?: combineTransform(account.flowHiddenUsers, note.flow().metadata.stateFlow) { hiddenUsers, metadata -> ?: combineTransform(
emit(metadata.note.isHiddenFor(hiddenUsers)) account.flowHiddenUsers,
account.liveKind3FollowsFlow,
note.flow().metadata.stateFlow,
note.flow().reports.stateFlow,
) { hiddenUsers, followingUsers, metadata, reports ->
emit(
isNoteAcceptable(metadata.note, hiddenUsers, followingUsers.users),
)
}.stateIn( }.stateIn(
viewModelScope, viewModelScope,
SharingStarted.Eagerly, SharingStarted.Eagerly,
false, NoteComposeReportState(),
).also { ).also {
noteIsHiddenFlows.put(note, it) noteIsHiddenFlows.put(note, it)
} }
@@ -761,52 +815,6 @@ class AccountViewModel(
fun defaultZapType(): LnZapEvent.ZapType = account.defaultZapType fun defaultZapType(): LnZapEvent.ZapType = account.defaultZapType
@Immutable
data class NoteComposeReportState(
val isAcceptable: Boolean = true,
val canPreview: Boolean = true,
val isHiddenAuthor: Boolean = false,
val relevantReports: ImmutableSet<Note> = persistentSetOf(),
)
suspend fun isNoteAcceptable(
note: Note,
onReady: (NoteComposeReportState) -> Unit,
) {
val newState =
withContext(Dispatchers.IO) {
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
NoteComposeReportState(true, true, false, persistentSetOf())
} else if (note.author?.let { account.isHidden(it) } == true) {
NoteComposeReportState(false, false, true, persistentSetOf())
} else {
val newCanPreview = !note.hasAnyReports()
val newIsAcceptable = account.isAcceptable(note)
if (newCanPreview && newIsAcceptable) {
// No need to process reports if nothing is wrong
NoteComposeReportState(true, true, false, persistentSetOf())
} else {
val newRelevantReports = account.getRelevantReports(note)
NoteComposeReportState(
newIsAcceptable,
newCanPreview,
false,
newRelevantReports.toImmutableSet(),
)
}
}
}
onReady(newState)
}
fun unwrap( fun unwrap(
event: GiftWrapEvent, event: GiftWrapEvent,
onReady: (Event) -> Unit, onReady: (Event) -> Unit,
@@ -24,6 +24,7 @@ import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
@@ -51,7 +52,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -70,14 +70,13 @@ import com.vitorpamplona.amethyst.ui.actions.NewPostView
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.navigation.routeFor
import com.vitorpamplona.amethyst.ui.note.BoostReaction import com.vitorpamplona.amethyst.ui.note.BoostReaction
import com.vitorpamplona.amethyst.ui.note.HiddenNote import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport
import com.vitorpamplona.amethyst.ui.note.LikeReaction import com.vitorpamplona.amethyst.ui.note.LikeReaction
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay
import com.vitorpamplona.amethyst.ui.note.RenderRelay import com.vitorpamplona.amethyst.ui.note.RenderRelay
import com.vitorpamplona.amethyst.ui.note.ReplyReaction import com.vitorpamplona.amethyst.ui.note.ReplyReaction
import com.vitorpamplona.amethyst.ui.note.ViewCountReaction import com.vitorpamplona.amethyst.ui.note.ViewCountReaction
import com.vitorpamplona.amethyst.ui.note.WatchForReports
import com.vitorpamplona.amethyst.ui.note.ZapReaction import com.vitorpamplona.amethyst.ui.note.ZapReaction
import com.vitorpamplona.amethyst.ui.note.elements.NoteDropDownMenu import com.vitorpamplona.amethyst.ui.note.elements.NoteDropDownMenu
import com.vitorpamplona.amethyst.ui.note.types.FileHeaderDisplay import com.vitorpamplona.amethyst.ui.note.types.FileHeaderDisplay
@@ -107,8 +106,6 @@ import com.vitorpamplona.quartz.events.FileHeaderEvent
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
import com.vitorpamplona.quartz.events.VideoEvent import com.vitorpamplona.quartz.events.VideoEvent
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@Composable @Composable
fun VideoScreen( fun VideoScreen(
@@ -249,75 +246,24 @@ fun SlidingCarousel(
key = { index -> feed.value.getOrNull(index)?.idHex ?: "$index" }, key = { index -> feed.value.getOrNull(index)?.idHex ?: "$index" },
) { index -> ) { index ->
feed.value.getOrNull(index)?.let { note -> feed.value.getOrNull(index)?.let { note ->
LoadedVideoCompose(note, showHidden, accountViewModel, nav) Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
} CheckHiddenFeedWatchBlockAndReport(
} note = note,
} modifier = Modifier.fillMaxWidth(),
showHiddenWarning = true,
@Composable ignoreAllBlocksAndReports = showHidden,
fun LoadedVideoCompose( accountViewModel = accountViewModel,
note: Note, nav = nav,
showHidden: Boolean, ) {
accountViewModel: AccountViewModel, RenderVideoOrPictureNote(
nav: (String) -> Unit, note,
) { accountViewModel,
var state by nav,
remember(note) { )
mutableStateOf( }
AccountViewModel.NoteComposeReportState(),
)
}
if (!showHidden) {
val scope = rememberCoroutineScope()
WatchForReports(note, accountViewModel) { newState ->
if (state != newState) {
scope.launch(Dispatchers.Main) { state = newState }
} }
} }
} }
Crossfade(targetState = state, label = "LoadedVideoCompose") {
RenderReportState(
it,
note,
accountViewModel,
nav,
)
}
}
@Composable
fun RenderReportState(
state: AccountViewModel.NoteComposeReportState,
note: Note,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
var showReportedNote by remember { mutableStateOf(false) }
Crossfade(targetState = (!state.isAcceptable || state.isHiddenAuthor) && !showReportedNote) {
showHiddenNote ->
if (showHiddenNote) {
Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center) {
HiddenNote(
state.relevantReports,
state.isHiddenAuthor,
accountViewModel,
Modifier.fillMaxWidth(),
nav,
onClick = { showReportedNote = true },
)
}
} else {
RenderVideoOrPictureNote(
note,
accountViewModel,
nav,
)
}
}
} }
@Composable @Composable
@@ -478,8 +424,7 @@ fun ReactionsColumn(
routeFor( routeFor(
baseNote, baseNote,
accountViewModel.userProfile(), accountViewModel.userProfile(),
) )?.let { nav(it) }
?.let { nav(it) }
} }
BoostReaction( BoostReaction(
baseNote = baseNote, baseNote = baseNote,