mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-04-22 14:34:12 +02:00
- 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:
parent
0d00f2f80a
commit
45445c03e5
@ -258,7 +258,9 @@ open class Note(
|
||||
if (repliesChanged) liveSet?.innerReplies?.invalidateData()
|
||||
if (reactionsChanged) liveSet?.innerReactions?.invalidateData()
|
||||
if (boostsChanged) liveSet?.innerBoosts?.invalidateData()
|
||||
if (reportsChanged) liveSet?.innerReports?.invalidateData()
|
||||
if (reportsChanged) {
|
||||
flowSet?.reports?.invalidateData()
|
||||
}
|
||||
if (zapsChanged) liveSet?.innerZaps?.invalidateData()
|
||||
|
||||
return toBeRemoved
|
||||
@ -290,7 +292,7 @@ open class Note(
|
||||
if (reports[author]?.contains(deleteNote) == true) {
|
||||
reports[author]?.let {
|
||||
reports = reports + Pair(author, it.minus(deleteNote))
|
||||
liveSet?.innerReports?.invalidateData()
|
||||
flowSet?.reports?.invalidateData()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -399,10 +401,10 @@ open class Note(
|
||||
|
||||
if (reportsByAuthor == null) {
|
||||
reports = reports + Pair(author, listOf(note))
|
||||
liveSet?.innerReports?.invalidateData()
|
||||
flowSet?.reports?.invalidateData()
|
||||
} else if (!reportsByAuthor.contains(note)) {
|
||||
reports = reports + Pair(author, reportsByAuthor + note)
|
||||
liveSet?.innerReports?.invalidateData()
|
||||
flowSet?.reports?.invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
@ -844,11 +846,13 @@ class NoteFlowSet(
|
||||
) {
|
||||
// Observers line up here.
|
||||
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() {
|
||||
metadata.destroy()
|
||||
reports.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
@ -861,7 +865,6 @@ class NoteLiveSet(
|
||||
val innerReactions = NoteBundledRefresherLiveData(u)
|
||||
val innerBoosts = NoteBundledRefresherLiveData(u)
|
||||
val innerReplies = NoteBundledRefresherLiveData(u)
|
||||
val innerReports = NoteBundledRefresherLiveData(u)
|
||||
val innerRelays = NoteBundledRefresherLiveData(u)
|
||||
val innerZaps = NoteBundledRefresherLiveData(u)
|
||||
val innerOts = NoteBundledRefresherLiveData(u)
|
||||
@ -871,7 +874,6 @@ class NoteLiveSet(
|
||||
val reactions = innerReactions.map { it }
|
||||
val boosts = innerBoosts.map { it }
|
||||
val replies = innerReplies.map { it }
|
||||
val reports = innerReports.map { it }
|
||||
val relays = innerRelays.map { it }
|
||||
val zaps = innerZaps.map { it }
|
||||
|
||||
@ -907,7 +909,6 @@ class NoteLiveSet(
|
||||
reactions.hasObservers() ||
|
||||
boosts.hasObservers() ||
|
||||
replies.hasObservers() ||
|
||||
reports.hasObservers() ||
|
||||
relays.hasObservers() ||
|
||||
zaps.hasObservers() ||
|
||||
hasEvent.hasObservers() ||
|
||||
@ -923,7 +924,6 @@ class NoteLiveSet(
|
||||
innerReactions.destroy()
|
||||
innerBoosts.destroy()
|
||||
innerReplies.destroy()
|
||||
innerReports.destroy()
|
||||
innerRelays.destroy()
|
||||
innerZaps.destroy()
|
||||
innerOts.destroy()
|
||||
|
@ -22,9 +22,7 @@ package com.vitorpamplona.amethyst.ui.note
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
@ -38,12 +36,12 @@ fun CheckHiddenFeedWatchBlockAndReport(
|
||||
note: Note,
|
||||
modifier: Modifier = Modifier,
|
||||
showHiddenWarning: Boolean,
|
||||
showHidden: Boolean = false,
|
||||
ignoreAllBlocksAndReports: Boolean = false,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
normalNote: @Composable (canPreview: Boolean) -> Unit,
|
||||
) {
|
||||
if (showHidden) {
|
||||
if (ignoreAllBlocksAndReports) {
|
||||
// Ignores reports as well
|
||||
normalNote(true)
|
||||
} else {
|
||||
@ -62,19 +60,28 @@ fun WatchBlockAndReport(
|
||||
nav: (String) -> Unit,
|
||||
normalNote: @Composable (canPreview: Boolean) -> Unit,
|
||||
) {
|
||||
val isHiddenState by accountViewModel.createIsHiddenFlow(note).collectAsStateWithLifecycle()
|
||||
val hiddenState by accountViewModel.createIsHiddenFlow(note).collectAsStateWithLifecycle()
|
||||
|
||||
val showAnyway =
|
||||
remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
Crossfade(targetState = isHiddenState, label = "CheckHiddenNoteCompose") { isHidden ->
|
||||
Crossfade(targetState = hiddenState, label = "CheckHiddenNoteCompose") { isHidden ->
|
||||
if (showAnyway.value) {
|
||||
normalNote(true)
|
||||
} else if (!isHidden) {
|
||||
LoadReportsNoteCompose(note, modifier, accountViewModel, nav) { canPreview ->
|
||||
normalNote(canPreview)
|
||||
} else if (!isHidden.isPostHidden) {
|
||||
if (isHidden.isAcceptable) {
|
||||
normalNote(isHidden.canPreview)
|
||||
} else {
|
||||
HiddenNote(
|
||||
isHidden.relevantReports,
|
||||
isHidden.isHiddenAuthor,
|
||||
accountViewModel,
|
||||
modifier,
|
||||
nav,
|
||||
onClick = { showAnyway.value = true },
|
||||
)
|
||||
}
|
||||
} else if (showHiddenWarning) {
|
||||
// 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.ui.Alignment
|
||||
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.TopEnd
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -124,7 +123,7 @@ fun ChannelCardCompose(
|
||||
CheckHiddenFeedWatchBlockAndReport(
|
||||
note = baseNote,
|
||||
modifier = modifier,
|
||||
showHidden = isHiddenFeed,
|
||||
ignoreAllBlocksAndReports = isHiddenFeed,
|
||||
showHiddenWarning = false,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
@ -308,8 +307,7 @@ fun RenderClassifiedsThumb(
|
||||
title = noteEvent?.title(),
|
||||
price = noteEvent?.price(),
|
||||
)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}.distinctUntilChanged()
|
||||
.observeAsState(
|
||||
ClassifiedsThumb(
|
||||
image = noteEvent.image(),
|
||||
@ -437,8 +435,7 @@ fun RenderLiveActivityThumb(
|
||||
status = noteEvent?.status(),
|
||||
starts = noteEvent?.starts(),
|
||||
)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}.distinctUntilChanged()
|
||||
.observeAsState(
|
||||
LiveActivityCard(
|
||||
name = noteEvent.dTag(),
|
||||
@ -567,8 +564,7 @@ fun RenderCommunitiesThumb(
|
||||
cover = noteEvent?.image()?.ifBlank { null },
|
||||
moderators = noteEvent?.moderators()?.toImmutableList() ?: persistentListOf(),
|
||||
)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}.distinctUntilChanged()
|
||||
.observeAsState(
|
||||
CommunityCard(
|
||||
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 =
|
||||
ParticipantListBuilder().followsThatParticipateOn(baseNote, followingKeySet).minus(hosts)
|
||||
|
||||
@ -723,7 +721,9 @@ private fun LoadParticipants(
|
||||
|
||||
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 =
|
||||
ParticipantListBuilder()
|
||||
@ -760,7 +760,11 @@ fun RenderContentDVMThumb(
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
// 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)
|
||||
|
||||
@ -875,7 +879,9 @@ fun RenderChannelThumb(
|
||||
|
||||
LaunchedEffect(key1 = channelUpdates) {
|
||||
launch(Dispatchers.IO) {
|
||||
val followingKeySet = accountViewModel.account.liveDiscoveryFollowLists.value?.users
|
||||
val followingKeySet =
|
||||
accountViewModel.account.liveDiscoveryFollowLists.value
|
||||
?.users
|
||||
val allParticipants =
|
||||
ParticipantListBuilder()
|
||||
.followsThatParticipateOn(baseNote, followingKeySet)
|
||||
|
@ -204,7 +204,7 @@ fun NoteCompose(
|
||||
CheckHiddenFeedWatchBlockAndReport(
|
||||
note = baseNote,
|
||||
modifier = modifier,
|
||||
showHidden = isHiddenFeed,
|
||||
ignoreAllBlocksAndReports = isHiddenFeed,
|
||||
showHiddenWarning = isQuotedNote || isBoostedNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
@ -264,9 +264,7 @@ fun AcceptableNote(
|
||||
}
|
||||
is BadgeDefinitionEvent -> BadgeDisplay(baseNote = baseNote)
|
||||
else ->
|
||||
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) {
|
||||
showPopup,
|
||||
->
|
||||
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup ->
|
||||
CheckNewAndRenderNote(
|
||||
baseNote = baseNote,
|
||||
routeForLastRead = routeForLastRead,
|
||||
|
@ -36,11 +36,8 @@ import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
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.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ProvideTextStyle
|
||||
@ -49,9 +46,9 @@ import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
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.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.ObserveDisplayNip05Status
|
||||
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.note.BlankNote
|
||||
import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport
|
||||
import com.vitorpamplona.amethyst.ui.note.DisplayDraft
|
||||
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.LongPressToQuickAction
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
|
||||
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.ReactionsRow
|
||||
import com.vitorpamplona.amethyst.ui.note.RenderDraft
|
||||
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.DisplayEditStatus
|
||||
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.DisplayZapSplits
|
||||
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.TimeAgo
|
||||
import com.vitorpamplona.amethyst.ui.note.observeEdits
|
||||
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.AudioTrackHeader
|
||||
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.DisplayRelaySet
|
||||
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.RenderGitPatchEvent
|
||||
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.RenderPinListEvent
|
||||
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.EditFieldTrailingIconModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size24Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
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.WikiNoteEvent
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
@ -225,7 +221,10 @@ fun RenderThreadFeed(
|
||||
LaunchedEffect(noteId) {
|
||||
// waits to load the thread to scroll to item.
|
||||
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)
|
||||
|
||||
if (position >= 0) {
|
||||
@ -321,347 +320,314 @@ fun Modifier.drawReplyLevel(
|
||||
}
|
||||
|
||||
return@drawBehind
|
||||
}
|
||||
.padding(start = (2 + (level * 3)).dp)
|
||||
}.padding(start = (2 + (level * 3)).dp)
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun NoteMaster(
|
||||
baseNote: Note,
|
||||
modifier: Modifier = Modifier,
|
||||
parentBackgroundColor: MutableState<Color>? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
val noteState by baseNote.live().metadata.observeAsState()
|
||||
val note = noteState?.note
|
||||
|
||||
val noteReportsState by baseNote.live().reports.observeAsState()
|
||||
val noteForReports = noteReportsState?.note ?: return
|
||||
|
||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||
val account = accountState?.account ?: return
|
||||
|
||||
var showHiddenNote by remember { mutableStateOf(false) }
|
||||
|
||||
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 = { },
|
||||
WatchNoteEvent(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
modifier,
|
||||
) {
|
||||
CheckHiddenFeedWatchBlockAndReport(
|
||||
note = baseNote,
|
||||
modifier = modifier,
|
||||
ignoreAllBlocksAndReports = false,
|
||||
showHiddenWarning = true,
|
||||
accountViewModel = accountViewModel,
|
||||
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)
|
||||
?: combineTransform(account.flowHiddenUsers, note.flow().metadata.stateFlow) { hiddenUsers, metadata ->
|
||||
emit(metadata.note.isHiddenFor(hiddenUsers))
|
||||
?: combineTransform(
|
||||
account.flowHiddenUsers,
|
||||
account.liveKind3FollowsFlow,
|
||||
note.flow().metadata.stateFlow,
|
||||
note.flow().reports.stateFlow,
|
||||
) { hiddenUsers, followingUsers, metadata, reports ->
|
||||
emit(
|
||||
isNoteAcceptable(metadata.note, hiddenUsers, followingUsers.users),
|
||||
)
|
||||
}.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.Eagerly,
|
||||
false,
|
||||
NoteComposeReportState(),
|
||||
).also {
|
||||
noteIsHiddenFlows.put(note, it)
|
||||
}
|
||||
@ -761,52 +815,6 @@ class AccountViewModel(
|
||||
|
||||
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(
|
||||
event: GiftWrapEvent,
|
||||
onReady: (Event) -> Unit,
|
||||
|
@ -24,6 +24,7 @@ import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
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.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
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.navigation.routeFor
|
||||
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.NoteAuthorPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.RenderRelay
|
||||
import com.vitorpamplona.amethyst.ui.note.ReplyReaction
|
||||
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.elements.NoteDropDownMenu
|
||||
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.VideoEvent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun VideoScreen(
|
||||
@ -249,75 +246,24 @@ fun SlidingCarousel(
|
||||
key = { index -> feed.value.getOrNull(index)?.idHex ?: "$index" },
|
||||
) { index ->
|
||||
feed.value.getOrNull(index)?.let { note ->
|
||||
LoadedVideoCompose(note, showHidden, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LoadedVideoCompose(
|
||||
note: Note,
|
||||
showHidden: Boolean,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
var state by
|
||||
remember(note) {
|
||||
mutableStateOf(
|
||||
AccountViewModel.NoteComposeReportState(),
|
||||
)
|
||||
}
|
||||
|
||||
if (!showHidden) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
WatchForReports(note, accountViewModel) { newState ->
|
||||
if (state != newState) {
|
||||
scope.launch(Dispatchers.Main) { state = newState }
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
CheckHiddenFeedWatchBlockAndReport(
|
||||
note = note,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
showHiddenWarning = true,
|
||||
ignoreAllBlocksAndReports = showHidden,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
) {
|
||||
RenderVideoOrPictureNote(
|
||||
note,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@ -478,8 +424,7 @@ fun ReactionsColumn(
|
||||
routeFor(
|
||||
baseNote,
|
||||
accountViewModel.userProfile(),
|
||||
)
|
||||
?.let { nav(it) }
|
||||
)?.let { nav(it) }
|
||||
}
|
||||
BoostReaction(
|
||||
baseNote = baseNote,
|
||||
|
Loading…
x
Reference in New Issue
Block a user