- Renders the report's comment in the user profile report tab
- Allows reply, zap, boost and like the report itself. - Shows report messages on notifications
This commit is contained in:
@@ -505,15 +505,6 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.content == "!" || // nostr_console hide.
|
|
||||||
event.content == "\u26A0\uFE0F" // Warning sign
|
|
||||||
) {
|
|
||||||
// Counts the replies
|
|
||||||
repliesTo.forEach {
|
|
||||||
it.addReport(note)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshObservers(note)
|
refreshObservers(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,9 +532,15 @@ object LocalCache {
|
|||||||
mentions.forEach {
|
mentions.forEach {
|
||||||
it.addReport(note)
|
it.addReport(note)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
repliesTo.forEach {
|
repliesTo.forEach {
|
||||||
it.addReport(note)
|
it.addReport(note)
|
||||||
|
}
|
||||||
|
|
||||||
|
mentions.forEach {
|
||||||
|
// doesn't add to reports, but triggers recounts
|
||||||
|
it.liveSet?.reports?.invalidateData()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshObservers(note)
|
refreshObservers(note)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import com.vitorpamplona.amethyst.model.*
|
import com.vitorpamplona.amethyst.model.*
|
||||||
import com.vitorpamplona.amethyst.service.FileHeader
|
import com.vitorpamplona.amethyst.service.FileHeader
|
||||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.model.BaseTextNoteEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
import com.vitorpamplona.amethyst.ui.components.isValidURL
|
import com.vitorpamplona.amethyst.ui.components.isValidURL
|
||||||
@@ -73,7 +74,12 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
open fun load(account: Account, replyingTo: Note?, quote: Note?) {
|
open fun load(account: Account, replyingTo: Note?, quote: Note?) {
|
||||||
originalNote = replyingTo
|
originalNote = replyingTo
|
||||||
replyingTo?.let { replyNote ->
|
replyingTo?.let { replyNote ->
|
||||||
this.replyTos = (replyNote.replyTo ?: emptyList()).plus(replyNote)
|
if (replyNote.event is BaseTextNoteEvent) {
|
||||||
|
this.replyTos = (replyNote.replyTo ?: emptyList()).plus(replyNote)
|
||||||
|
} else {
|
||||||
|
this.replyTos = listOf(replyNote)
|
||||||
|
}
|
||||||
|
|
||||||
replyNote.author?.let { replyUser ->
|
replyNote.author?.let { replyUser ->
|
||||||
val currentMentions = (replyNote.event as? TextNoteEvent)
|
val currentMentions = (replyNote.event as? TextNoteEvent)
|
||||||
?.mentions()
|
?.mentions()
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.dal
|
package com.vitorpamplona.amethyst.ui.dal
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||||
|
|
||||||
object UserProfileReportsFeedFilter : FeedFilter<Note>() {
|
object UserProfileReportsFeedFilter : FeedFilter<Note>() {
|
||||||
var user: User? = null
|
var user: User? = null
|
||||||
@@ -11,10 +13,12 @@ object UserProfileReportsFeedFilter : FeedFilter<Note>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun feed(): List<Note> {
|
override fun feed(): List<Note> {
|
||||||
return user?.reports
|
val myUser = user ?: return emptyList()
|
||||||
?.values
|
|
||||||
?.flatten()
|
val reportNotes = LocalCache.notes.values.filter { (it.event as? ReportEvent)?.isTaggedUser(myUser.pubkeyHex) == true }
|
||||||
?.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
|
|
||||||
?.reversed() ?: emptyList()
|
return reportNotes
|
||||||
|
.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
|
||||||
|
.reversed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,8 +43,10 @@ import androidx.compose.material.icons.filled.MoreVert
|
|||||||
import androidx.compose.material.icons.filled.PushPin
|
import androidx.compose.material.icons.filled.PushPin
|
||||||
import androidx.compose.material.lightColors
|
import androidx.compose.material.lightColors
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
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.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -102,7 +104,6 @@ import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
|||||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReportedKey
|
|
||||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
||||||
@@ -152,18 +153,10 @@ fun NoteCompose(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
||||||
val account = remember(accountState) { accountState?.account } ?: return
|
|
||||||
|
|
||||||
val noteState by baseNote.live().metadata.observeAsState()
|
val noteState by baseNote.live().metadata.observeAsState()
|
||||||
val note = remember(noteState) { noteState?.note } ?: return
|
val noteEvent = remember(noteState) { noteState?.note?.event }
|
||||||
|
|
||||||
val noteReportsState by baseNote.live().reports.observeAsState()
|
if (noteEvent == null) {
|
||||||
val noteForReports = remember(noteReportsState) { noteReportsState?.note } ?: return
|
|
||||||
|
|
||||||
val isSensitive = remember(noteState) { note.event?.isSensitive() ?: false }
|
|
||||||
|
|
||||||
if (note.event == null) {
|
|
||||||
var popupExpanded by remember { mutableStateOf(false) }
|
var popupExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
BlankNote(
|
BlankNote(
|
||||||
@@ -176,55 +169,150 @@ fun NoteCompose(
|
|||||||
isBoostedNote
|
isBoostedNote
|
||||||
)
|
)
|
||||||
|
|
||||||
NoteQuickActionMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
|
NoteQuickActionMenu(baseNote, popupExpanded, { popupExpanded = false }, accountViewModel)
|
||||||
} else if (account.isHidden(noteForReports.author!!) || (isSensitive && account.showSensitiveContent == false)) {
|
|
||||||
// Does nothing
|
|
||||||
} else {
|
} else {
|
||||||
var showReportedNote by remember { mutableStateOf(false) }
|
CheckHiddenNoteCompose(
|
||||||
var isAcceptableAndCanPreview by remember { mutableStateOf(Pair(true, true)) }
|
baseNote,
|
||||||
|
routeForLastRead,
|
||||||
|
modifier,
|
||||||
|
isBoostedNote,
|
||||||
|
isQuotedNote,
|
||||||
|
unPackReply,
|
||||||
|
makeItShort,
|
||||||
|
addMarginTop,
|
||||||
|
parentBackgroundColor,
|
||||||
|
accountViewModel,
|
||||||
|
nav
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = noteReportsState, key2 = accountState) {
|
@Composable
|
||||||
withContext(Dispatchers.IO) {
|
fun CheckHiddenNoteCompose(
|
||||||
account.userProfile().let { loggedIn ->
|
note: Note,
|
||||||
val newCanPreview = note.author?.pubkeyHex == loggedIn.pubkeyHex ||
|
routeForLastRead: String? = null,
|
||||||
(note.author?.let { loggedIn.isFollowingCached(it) } ?: true) ||
|
modifier: Modifier = Modifier,
|
||||||
!(noteForReports.hasAnyReports())
|
isBoostedNote: Boolean = false,
|
||||||
|
isQuotedNote: Boolean = false,
|
||||||
|
unPackReply: Boolean = true,
|
||||||
|
makeItShort: Boolean = false,
|
||||||
|
addMarginTop: Boolean = true,
|
||||||
|
parentBackgroundColor: Color? = null,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
val account = remember(accountState) { accountState?.account } ?: return
|
||||||
|
|
||||||
val newIsAcceptable = account.isAcceptable(noteForReports)
|
val isHidden by remember(accountState) {
|
||||||
|
derivedStateOf {
|
||||||
|
val isSensitive = note.event?.isSensitive() ?: false
|
||||||
|
|
||||||
if (newIsAcceptable != isAcceptableAndCanPreview.first && newCanPreview != isAcceptableAndCanPreview.second) {
|
account.isHidden(note.author!!) || (isSensitive && account.showSensitiveContent == false)
|
||||||
isAcceptableAndCanPreview = Pair(newIsAcceptable, newCanPreview)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isHidden) {
|
||||||
|
LoadedNoteCompose(
|
||||||
|
note,
|
||||||
|
routeForLastRead,
|
||||||
|
modifier,
|
||||||
|
isBoostedNote,
|
||||||
|
isQuotedNote,
|
||||||
|
unPackReply,
|
||||||
|
makeItShort,
|
||||||
|
addMarginTop,
|
||||||
|
parentBackgroundColor,
|
||||||
|
accountViewModel,
|
||||||
|
nav
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class NoteComposeReportState(
|
||||||
|
val isAcceptable: Boolean,
|
||||||
|
val canPreview: Boolean,
|
||||||
|
val relevantReports: Set<Note>
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoadedNoteCompose(
|
||||||
|
note: Note,
|
||||||
|
routeForLastRead: String? = null,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
isBoostedNote: Boolean = false,
|
||||||
|
isQuotedNote: Boolean = false,
|
||||||
|
unPackReply: Boolean = true,
|
||||||
|
makeItShort: Boolean = false,
|
||||||
|
addMarginTop: Boolean = true,
|
||||||
|
parentBackgroundColor: Color? = null,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
val account = remember(accountState) { accountState?.account } ?: return
|
||||||
|
|
||||||
|
val noteReportsState by note.live().reports.observeAsState()
|
||||||
|
val noteForReports = remember(noteReportsState) { noteReportsState?.note } ?: return
|
||||||
|
|
||||||
|
var showReportedNote by remember { mutableStateOf(false) }
|
||||||
|
var state by remember { mutableStateOf(NoteComposeReportState(true, true, emptySet())) }
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = noteReportsState, key2 = accountState) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
account.userProfile().let { loggedIn ->
|
||||||
|
val newCanPreview = note.author?.pubkeyHex == loggedIn.pubkeyHex ||
|
||||||
|
(note.author?.let { loggedIn.isFollowingCached(it) } ?: true) ||
|
||||||
|
!(noteForReports.hasAnyReports())
|
||||||
|
|
||||||
|
val newIsAcceptable = account.isAcceptable(noteForReports)
|
||||||
|
val newRelevantReports = account.getRelevantReports(noteForReports)
|
||||||
|
|
||||||
|
if (newIsAcceptable != state.isAcceptable || newCanPreview != state.canPreview) {
|
||||||
|
state = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isAcceptableAndCanPreview.first && !showReportedNote) {
|
val showHiddenNote by remember(state, showReportedNote) {
|
||||||
HiddenNote(
|
derivedStateOf {
|
||||||
account.getRelevantReports(noteForReports),
|
!state.isAcceptable && !showReportedNote
|
||||||
account.userProfile(),
|
|
||||||
modifier,
|
|
||||||
isBoostedNote,
|
|
||||||
nav,
|
|
||||||
onClick = { showReportedNote = true }
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
NormalNote(
|
|
||||||
baseNote,
|
|
||||||
routeForLastRead,
|
|
||||||
modifier,
|
|
||||||
isBoostedNote,
|
|
||||||
isQuotedNote,
|
|
||||||
unPackReply,
|
|
||||||
makeItShort,
|
|
||||||
addMarginTop,
|
|
||||||
isAcceptableAndCanPreview.second,
|
|
||||||
parentBackgroundColor,
|
|
||||||
accountViewModel,
|
|
||||||
nav
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showHiddenNote) {
|
||||||
|
HiddenNote(
|
||||||
|
state.relevantReports,
|
||||||
|
accountViewModel.userProfile(),
|
||||||
|
modifier,
|
||||||
|
isBoostedNote,
|
||||||
|
nav,
|
||||||
|
onClick = { showReportedNote = true }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val canPreview by remember(state, showReportedNote) {
|
||||||
|
derivedStateOf {
|
||||||
|
(!state.isAcceptable && showReportedNote) || state.canPreview
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NormalNote(
|
||||||
|
note,
|
||||||
|
routeForLastRead,
|
||||||
|
modifier,
|
||||||
|
isBoostedNote,
|
||||||
|
isQuotedNote,
|
||||||
|
unPackReply,
|
||||||
|
makeItShort,
|
||||||
|
addMarginTop,
|
||||||
|
canPreview,
|
||||||
|
parentBackgroundColor,
|
||||||
|
accountViewModel,
|
||||||
|
nav
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@@ -373,7 +461,7 @@ fun NormalNote(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is ReportEvent -> {
|
is ReportEvent -> {
|
||||||
RenderReport(baseNote)
|
RenderReport(baseNote, backgroundColor, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
is LongTextNoteEvent -> {
|
is LongTextNoteEvent -> {
|
||||||
@@ -1045,14 +1133,16 @@ private fun RenderLongFormContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RenderReport(note: Note) {
|
private fun RenderReport(
|
||||||
|
note: Note,
|
||||||
|
backgroundColor: Color,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val noteEvent = note.event as? ReportEvent ?: return
|
||||||
|
|
||||||
val base = remember {
|
val base = remember {
|
||||||
val noteEvent = note.event as? ReportEvent
|
(noteEvent.reportedPost() + noteEvent.reportedAuthor())
|
||||||
if (noteEvent == null) {
|
|
||||||
emptyList<ReportedKey>()
|
|
||||||
} else {
|
|
||||||
(noteEvent.reportedPost() + noteEvent.reportedAuthor())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val reportType = base.map {
|
val reportType = base.map {
|
||||||
@@ -1066,12 +1156,45 @@ private fun RenderReport(note: Note) {
|
|||||||
}
|
}
|
||||||
}.toSet().joinToString(", ")
|
}.toSet().joinToString(", ")
|
||||||
|
|
||||||
Text(
|
val content = remember {
|
||||||
text = reportType
|
reportType + (note.event?.content()?.ifBlank { null }?.let { ": $it" } ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
TranslatableRichTextViewer(
|
||||||
|
content = content,
|
||||||
|
canPreview = true,
|
||||||
|
modifier = remember { Modifier },
|
||||||
|
tags = emptyList(),
|
||||||
|
backgroundColor = backgroundColor,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav
|
||||||
)
|
)
|
||||||
|
|
||||||
|
note.replyTo?.lastOrNull()?.let {
|
||||||
|
NoteCompose(
|
||||||
|
baseNote = it,
|
||||||
|
isQuotedNote = true,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 5.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(shape = RoundedCornerShape(15.dp))
|
||||||
|
.border(
|
||||||
|
1.dp,
|
||||||
|
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
|
||||||
|
RoundedCornerShape(15.dp)
|
||||||
|
),
|
||||||
|
unPackReply = false,
|
||||||
|
makeItShort = true,
|
||||||
|
parentBackgroundColor = backgroundColor,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactionsRow(note, accountViewModel, nav)
|
||||||
|
|
||||||
Divider(
|
Divider(
|
||||||
modifier = Modifier.padding(top = 40.dp),
|
modifier = Modifier.padding(top = 10.dp),
|
||||||
thickness = 0.25.dp
|
thickness = 0.25.dp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -232,7 +232,9 @@ fun ProfileScreen(user: User, accountViewModel: AccountViewModel, nav: (String)
|
|||||||
tabs.forEachIndexed { index, function ->
|
tabs.forEachIndexed { index, function ->
|
||||||
Tab(
|
Tab(
|
||||||
selected = pagerState.currentPage == index,
|
selected = pagerState.currentPage == index,
|
||||||
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(index) } },
|
onClick = {
|
||||||
|
coroutineScope.launch { pagerState.animateScrollToPage(index) }
|
||||||
|
},
|
||||||
text = function
|
text = function
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -275,7 +277,16 @@ private fun RelaysTabHeader(baseUser: User) {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun ReportsTabHeader(baseUser: User) {
|
private fun ReportsTabHeader(baseUser: User) {
|
||||||
val userState by baseUser.live().reports.observeAsState()
|
val userState by baseUser.live().reports.observeAsState()
|
||||||
val userReports = remember(userState) { userState?.user?.reports?.values?.flatten()?.count() }
|
var userReports by remember { mutableStateOf(0) }
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = userState) {
|
||||||
|
UserProfileReportsFeedFilter.user = baseUser
|
||||||
|
val newSize = UserProfileReportsFeedFilter.feed().size
|
||||||
|
|
||||||
|
if (newSize != userReports) {
|
||||||
|
userReports = newSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text(text = "$userReports ${stringResource(R.string.reports)}")
|
Text(text = "$userReports ${stringResource(R.string.reports)}")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user