- 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
pull/432/head
Vitor Pamplona 2023-05-30 12:03:33 -04:00
rodzic 7ad982ec71
commit 5ab94e8a1c
5 zmienionych plików z 222 dodań i 81 usunięć

Wyświetl plik

@ -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)
}
@ -541,9 +532,15 @@ object LocalCache {
mentions.forEach {
it.addReport(note)
}
}
repliesTo.forEach {
it.addReport(note)
} else {
repliesTo.forEach {
it.addReport(note)
}
mentions.forEach {
// doesn't add to reports, but triggers recounts
it.liveSet?.reports?.invalidateData()
}
}
refreshObservers(note)

Wyświetl plik

@ -15,6 +15,7 @@ import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.model.*
import com.vitorpamplona.amethyst.service.FileHeader
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.TextNoteEvent
import com.vitorpamplona.amethyst.ui.components.isValidURL
@ -73,7 +74,12 @@ open class NewPostViewModel : ViewModel() {
open fun load(account: Account, replyingTo: Note?, quote: Note?) {
originalNote = replyingTo
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 ->
val currentMentions = (replyNote.event as? TextNoteEvent)
?.mentions()

Wyświetl plik

@ -1,7 +1,9 @@
package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.model.ReportEvent
object UserProfileReportsFeedFilter : FeedFilter<Note>() {
var user: User? = null
@ -11,10 +13,12 @@ object UserProfileReportsFeedFilter : FeedFilter<Note>() {
}
override fun feed(): List<Note> {
return user?.reports
?.values
?.flatten()
?.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
?.reversed() ?: emptyList()
val myUser = user ?: return emptyList()
val reportNotes = LocalCache.notes.values.filter { (it.event as? ReportEvent)?.isTaggedUser(myUser.pubkeyHex) == true }
return reportNotes
.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
.reversed()
}
}

Wyświetl plik

@ -43,8 +43,10 @@ import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.PushPin
import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
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.ReactionEvent
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.TextNoteEvent
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
@ -152,18 +153,10 @@ fun NoteCompose(
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = remember(accountState) { accountState?.account } ?: return
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()
val noteForReports = remember(noteReportsState) { noteReportsState?.note } ?: return
val isSensitive = remember(noteState) { note.event?.isSensitive() ?: false }
if (note.event == null) {
if (noteEvent == null) {
var popupExpanded by remember { mutableStateOf(false) }
BlankNote(
@ -176,55 +169,150 @@ fun NoteCompose(
isBoostedNote
)
NoteQuickActionMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
} else if (account.isHidden(noteForReports.author!!) || (isSensitive && account.showSensitiveContent == false)) {
// Does nothing
NoteQuickActionMenu(baseNote, popupExpanded, { popupExpanded = false }, accountViewModel)
} else {
var showReportedNote by remember { mutableStateOf(false) }
var isAcceptableAndCanPreview by remember { mutableStateOf(Pair(true, true)) }
CheckHiddenNoteCompose(
baseNote,
routeForLastRead,
modifier,
isBoostedNote,
isQuotedNote,
unPackReply,
makeItShort,
addMarginTop,
parentBackgroundColor,
accountViewModel,
nav
)
}
}
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())
@Composable
fun CheckHiddenNoteCompose(
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 newIsAcceptable = account.isAcceptable(noteForReports)
val isHidden by remember(accountState) {
derivedStateOf {
val isSensitive = note.event?.isSensitive() ?: false
if (newIsAcceptable != isAcceptableAndCanPreview.first && newCanPreview != isAcceptableAndCanPreview.second) {
isAcceptableAndCanPreview = Pair(newIsAcceptable, newCanPreview)
}
account.isHidden(note.author!!) || (isSensitive && account.showSensitiveContent == false)
}
}
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) {
HiddenNote(
account.getRelevantReports(noteForReports),
account.userProfile(),
modifier,
isBoostedNote,
nav,
onClick = { showReportedNote = true }
)
} else {
NormalNote(
baseNote,
routeForLastRead,
modifier,
isBoostedNote,
isQuotedNote,
unPackReply,
makeItShort,
addMarginTop,
isAcceptableAndCanPreview.second,
parentBackgroundColor,
accountViewModel,
nav
)
val showHiddenNote by remember(state, showReportedNote) {
derivedStateOf {
!state.isAcceptable && !showReportedNote
}
}
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)
@ -373,7 +461,7 @@ fun NormalNote(
}
is ReportEvent -> {
RenderReport(baseNote)
RenderReport(baseNote, backgroundColor, accountViewModel, nav)
}
is LongTextNoteEvent -> {
@ -1045,14 +1133,16 @@ private fun RenderLongFormContent(
}
@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 noteEvent = note.event as? ReportEvent
if (noteEvent == null) {
emptyList<ReportedKey>()
} else {
(noteEvent.reportedPost() + noteEvent.reportedAuthor())
}
(noteEvent.reportedPost() + noteEvent.reportedAuthor())
}
val reportType = base.map {
@ -1066,12 +1156,45 @@ private fun RenderReport(note: Note) {
}
}.toSet().joinToString(", ")
Text(
text = reportType
val content = remember {
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(
modifier = Modifier.padding(top = 40.dp),
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
)
}

Wyświetl plik

@ -232,7 +232,9 @@ fun ProfileScreen(user: User, accountViewModel: AccountViewModel, nav: (String)
tabs.forEachIndexed { index, function ->
Tab(
selected = pagerState.currentPage == index,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(index) } },
onClick = {
coroutineScope.launch { pagerState.animateScrollToPage(index) }
},
text = function
)
}
@ -275,7 +277,16 @@ private fun RelaysTabHeader(baseUser: User) {
@Composable
private fun ReportsTabHeader(baseUser: User) {
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)}")
}