mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-03-26 17:52:29 +01:00
Activating NIP 56 (Report Users and Posts with Event Kind 1984)
This commit is contained in:
parent
9fe73c7a97
commit
8b1e0f9af0
@ -6,6 +6,7 @@ import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||
import com.vitorpamplona.amethyst.service.relays.Client
|
||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||
@ -102,7 +103,7 @@ class Account(
|
||||
}
|
||||
}
|
||||
|
||||
fun report(note: Note) {
|
||||
fun report(note: Note, type: ReportEvent.ReportType) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
if (
|
||||
@ -117,6 +118,27 @@ class Account(
|
||||
Client.send(event)
|
||||
LocalCache.consume(event)
|
||||
}
|
||||
|
||||
note.event?.let {
|
||||
val event = ReportEvent.create(it, type, loggedIn.privKey!!)
|
||||
Client.send(event)
|
||||
LocalCache.consume(event)
|
||||
}
|
||||
}
|
||||
|
||||
fun report(user: User, type: ReportEvent.ReportType) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
if (
|
||||
user.reports.firstOrNull { it.author == userProfile() && it.event is ReportEvent && (it.event as ReportEvent).reportType.contains(type) } != null
|
||||
) {
|
||||
// has already reported this note
|
||||
return
|
||||
}
|
||||
|
||||
val event = ReportEvent.create(user.pubkeyHex, type, loggedIn.privKey!!)
|
||||
Client.send(event)
|
||||
LocalCache.consume(event)
|
||||
}
|
||||
|
||||
fun boost(note: Note) {
|
||||
@ -347,9 +369,12 @@ class Account(
|
||||
}
|
||||
}
|
||||
|
||||
fun isHidden(user: User) = user !in hiddenUsers()
|
||||
|
||||
fun isAcceptable(user: User): Boolean {
|
||||
return user !in hiddenUsers() // if user hasn't hided this author
|
||||
&& user.reports.firstOrNull { it.author == userProfile() } == null // if user has not reported this post
|
||||
&& user.reports.filter { it.author in userProfile().follows }.size < 5
|
||||
}
|
||||
|
||||
fun isAcceptableDirect(note: Note): Boolean {
|
||||
@ -364,6 +389,7 @@ class Account(
|
||||
|| (note.event is RepostEvent && note.replyTo?.firstOrNull { isAcceptableDirect(note) } != null)
|
||||
) // is not a reaction about a blocked post
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AccountLiveData(private val account: Account): LiveData<AccountState>(AccountState(account)) {
|
||||
|
@ -11,6 +11,7 @@ import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMuteUserEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.time.Instant
|
||||
@ -273,6 +274,28 @@ object LocalCache {
|
||||
}
|
||||
}
|
||||
|
||||
fun consume(event: ReportEvent) {
|
||||
val note = getOrCreateNote(event.id.toHex())
|
||||
|
||||
// Already processed this event.
|
||||
if (note.event != null) return
|
||||
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
val mentions = event.reportedAuthor.map { getOrCreateUser(decodePublicKey(it)) }
|
||||
val repliesTo = event.reportedPost.map { getOrCreateNote(it) }.toMutableList()
|
||||
|
||||
note.loadEvent(event, author, mentions, repliesTo)
|
||||
|
||||
//Log.d("RP", "New Report ${event.content} by ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
||||
// Adds notifications to users.
|
||||
mentions.forEach {
|
||||
it.addReport(note)
|
||||
}
|
||||
repliesTo.forEach {
|
||||
it.addReport(note)
|
||||
}
|
||||
}
|
||||
|
||||
fun consume(event: ChannelCreateEvent) {
|
||||
//Log.d("MT", "New Event ${event.content} ${event.id.toHex()}")
|
||||
// new event
|
||||
|
@ -35,7 +35,6 @@ class Note(val idHex: String) {
|
||||
val replies = Collections.synchronizedSet(mutableSetOf<Note>())
|
||||
val reactions = Collections.synchronizedSet(mutableSetOf<Note>())
|
||||
val boosts = Collections.synchronizedSet(mutableSetOf<Note>())
|
||||
|
||||
val reports = Collections.synchronizedSet(mutableSetOf<Note>())
|
||||
|
||||
var channel: Channel? = null
|
||||
|
@ -38,6 +38,8 @@ class User(val pubkey: ByteArray) {
|
||||
val followers = Collections.synchronizedSet(mutableSetOf<User>())
|
||||
val messages = ConcurrentHashMap<User, MutableSet<Note>>()
|
||||
|
||||
val reports = Collections.synchronizedSet(mutableSetOf<Note>())
|
||||
|
||||
fun toBestDisplayName(): String {
|
||||
return bestDisplayName() ?: bestUsername() ?: pubkeyDisplayHex
|
||||
}
|
||||
@ -83,6 +85,13 @@ class User(val pubkey: ByteArray) {
|
||||
updateSubscribers { it.onNewPosts() }
|
||||
}
|
||||
|
||||
fun addReport(note: Note) {
|
||||
if (reports.add(note)) {
|
||||
updateSubscribers { it.onNewReports() }
|
||||
invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getOrCreateChannel(user: User): MutableSet<Note> {
|
||||
return messages[user] ?: run {
|
||||
@ -169,6 +178,7 @@ class User(val pubkey: ByteArray) {
|
||||
open fun onFollowsChange() = Unit
|
||||
open fun onNewPosts() = Unit
|
||||
open fun onNewMessage() = Unit
|
||||
open fun onNewReports() = Unit
|
||||
}
|
||||
|
||||
// Refreshes observers in batches.
|
||||
|
@ -11,6 +11,7 @@ import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMuteUserEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||
import com.vitorpamplona.amethyst.service.relays.Client
|
||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||
@ -66,6 +67,7 @@ abstract class NostrDataSource<T>(val debugName: String) {
|
||||
else -> when (event.kind) {
|
||||
RepostEvent.kind -> LocalCache.consume(RepostEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig))
|
||||
ReactionEvent.kind -> LocalCache.consume(ReactionEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig))
|
||||
ReportEvent.kind -> LocalCache.consume(ReportEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig))
|
||||
|
||||
ChannelCreateEvent.kind -> LocalCache.consume(ChannelCreateEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig))
|
||||
ChannelMetadataEvent.kind -> LocalCache.consume(ChannelMetadataEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig))
|
||||
|
@ -6,6 +6,7 @@ import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||
import java.util.Collections
|
||||
import nostr.postr.JsonFilter
|
||||
@ -24,7 +25,7 @@ object NostrSingleEventDataSource: NostrDataSource<Note>("SingleEventFeed") {
|
||||
// downloads all the reactions to a given event.
|
||||
return JsonFilter(
|
||||
kinds = listOf(
|
||||
TextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind
|
||||
TextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind, ReportEvent.kind
|
||||
),
|
||||
tags = mapOf("e" to reactionsToWatch)
|
||||
)
|
||||
|
@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.service
|
||||
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
|
||||
import java.util.Collections
|
||||
import nostr.postr.JsonFilter
|
||||
import nostr.postr.events.MetadataEvent
|
||||
@ -22,7 +23,19 @@ object NostrSingleUserDataSource: NostrDataSource<User>("SingleUserFeed") {
|
||||
}
|
||||
}
|
||||
|
||||
fun createUserReportFilter(): List<JsonFilter>? {
|
||||
if (usersToWatch.isEmpty()) return null
|
||||
|
||||
return usersToWatch.map {
|
||||
JsonFilter(
|
||||
kinds = listOf(ReportEvent.kind),
|
||||
tags = mapOf("p" to listOf(it))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val userChannel = requestNewChannel()
|
||||
val userReportChannel = requestNewChannel()
|
||||
|
||||
override fun feed(): List<User> {
|
||||
return synchronized(usersToWatch) {
|
||||
@ -34,6 +47,7 @@ object NostrSingleUserDataSource: NostrDataSource<User>("SingleUserFeed") {
|
||||
|
||||
override fun updateChannelFilters() {
|
||||
userChannel.filter = createUserFilter()
|
||||
userReportChannel.filter = createUserReportFilter()
|
||||
}
|
||||
|
||||
fun add(userId: String) {
|
||||
|
@ -0,0 +1,66 @@
|
||||
package com.vitorpamplona.amethyst.service.model
|
||||
|
||||
import androidx.compose.ui.text.toUpperCase
|
||||
import java.util.Date
|
||||
import nostr.postr.Utils
|
||||
import nostr.postr.events.Event
|
||||
import nostr.postr.toHex
|
||||
|
||||
// NIP 56 event.
|
||||
class ReportEvent (
|
||||
id: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
createdAt: Long,
|
||||
tags: List<List<String>>,
|
||||
content: String,
|
||||
sig: ByteArray
|
||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||
|
||||
@Transient val reportType: List<ReportType>
|
||||
@Transient val reportedPost: List<String>
|
||||
@Transient val reportedAuthor: List<String>
|
||||
|
||||
init {
|
||||
reportType = tags.filter { it.firstOrNull() == "report" }.mapNotNull { it.getOrNull(1) }.map { ReportType.valueOf(it.toUpperCase()) }
|
||||
reportedPost = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
||||
reportedAuthor = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val kind = 1984
|
||||
|
||||
fun create(reportedPost: Event, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent {
|
||||
val content = ""
|
||||
|
||||
val reportTypeTag = listOf("report", type.name.toLowerCase())
|
||||
val reportPostTag = listOf("e", reportedPost.id.toHex())
|
||||
val reportAuthorTag = listOf("p", reportedPost.pubKey.toHex())
|
||||
|
||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
||||
val tags:List<List<String>> = listOf(reportTypeTag, reportPostTag, reportAuthorTag)
|
||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||
val sig = Utils.sign(id, privateKey)
|
||||
return ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
}
|
||||
|
||||
fun create(reportedUser: String, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent {
|
||||
val content = ""
|
||||
|
||||
val reportTypeTag = listOf("report", type.name.toLowerCase())
|
||||
val reportAuthorTag = listOf("p", reportedUser)
|
||||
|
||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
||||
val tags:List<List<String>> = listOf(reportTypeTag, reportAuthorTag)
|
||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||
val sig = Utils.sign(id, privateKey)
|
||||
return ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
}
|
||||
}
|
||||
|
||||
enum class ReportType() {
|
||||
EXPLICIT,
|
||||
ILLEGAL,
|
||||
SPAM,
|
||||
IMPERSONATION
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import android.util.Patterns
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.Text
|
||||
@ -46,6 +47,7 @@ fun isValidURL(url: String?): Boolean {
|
||||
@Composable
|
||||
fun RichTextViewer(content: String, tags: List<List<String>>?, navController: NavController) {
|
||||
Column(modifier = Modifier.padding(top = 5.dp)) {
|
||||
|
||||
// FlowRow doesn't work well with paragraphs. So we need to split them
|
||||
content.split('\n').forEach { paragraph ->
|
||||
|
||||
|
@ -20,6 +20,7 @@ import androidx.compose.material.DropdownMenu
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.LocalContentColor
|
||||
import androidx.compose.material.LocalTextStyle
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@ -38,6 +39,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
@ -51,6 +53,7 @@ import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.model.toNote
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||
import com.vitorpamplona.amethyst.ui.components.RichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
@ -74,6 +77,9 @@ fun NoteCompose(
|
||||
val noteState by baseNote.live.observeAsState()
|
||||
val note = noteState?.note
|
||||
|
||||
val noteReportsState by baseNote.liveReports.observeAsState()
|
||||
val noteForReports = noteReportsState?.note ?: return
|
||||
|
||||
var popupExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
val context = LocalContext.current.applicationContext
|
||||
@ -83,7 +89,7 @@ fun NoteCompose(
|
||||
onClick = { },
|
||||
onLongClick = { popupExpanded = true },
|
||||
), isInnerNote)
|
||||
} else if (account?.isAcceptable(note) == false) {
|
||||
} else if (!account.isAcceptable(noteForReports)) {
|
||||
HiddenNote(modifier.combinedClickable(
|
||||
onClick = { },
|
||||
onLongClick = { popupExpanded = true },
|
||||
@ -239,8 +245,20 @@ fun NoteCompose(
|
||||
}
|
||||
} else {
|
||||
val eventContent = note.event?.content
|
||||
if (eventContent != null)
|
||||
RichTextViewer(eventContent, note.event?.tags, navController)
|
||||
if (eventContent != null) {
|
||||
if (note.reports.size > 0) {
|
||||
// Doesn't load images
|
||||
Row() {
|
||||
Text(
|
||||
text = eventContent,
|
||||
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
RichTextViewer(eventContent, note.event?.tags, navController)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//if (note.event !is ChannelMessageEvent) {
|
||||
ReactionsRow(note, accountViewModel)
|
||||
@ -364,7 +382,7 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
|
||||
Text("Copy Text")
|
||||
}
|
||||
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.author?.pubkey?.toNpub() ?: "")); onDismiss() }) {
|
||||
Text("Copy User ID")
|
||||
Text("Copy User PubKey")
|
||||
}
|
||||
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.id.toNote())); onDismiss() }) {
|
||||
Text("Copy Note ID")
|
||||
@ -374,11 +392,37 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
|
||||
Text("Broadcast")
|
||||
}
|
||||
Divider()
|
||||
DropdownMenuItem(onClick = { accountViewModel.report(note); onDismiss() }) {
|
||||
Text("Report Post")
|
||||
}
|
||||
DropdownMenuItem(onClick = { note.author?.let { accountViewModel.hide(it, context) }; onDismiss() }) {
|
||||
Text("Hide User")
|
||||
}
|
||||
Divider()
|
||||
DropdownMenuItem(onClick = {
|
||||
accountViewModel.report(note, ReportEvent.ReportType.SPAM);
|
||||
note.author?.let { accountViewModel.hide(it, context) }
|
||||
onDismiss()
|
||||
}) {
|
||||
Text("Report Spam / Scam")
|
||||
}
|
||||
DropdownMenuItem(onClick = {
|
||||
accountViewModel.report(note, ReportEvent.ReportType.IMPERSONATION);
|
||||
note.author?.let { accountViewModel.hide(it, context) }
|
||||
onDismiss()
|
||||
}) {
|
||||
Text("Report Impersonation")
|
||||
}
|
||||
DropdownMenuItem(onClick = {
|
||||
accountViewModel.report(note, ReportEvent.ReportType.EXPLICIT);
|
||||
note.author?.let { accountViewModel.hide(it, context) }
|
||||
onDismiss()
|
||||
}) {
|
||||
Text("Report Explicit Content")
|
||||
}
|
||||
DropdownMenuItem(onClick = {
|
||||
accountViewModel.report(note, ReportEvent.ReportType.ILLEGAL);
|
||||
note.author?.let { accountViewModel.hide(it, context) }
|
||||
onDismiss()
|
||||
}) {
|
||||
Text("Report Illegal Behaviour")
|
||||
}
|
||||
}
|
||||
}
|
@ -68,7 +68,7 @@ fun UserCompose(baseUser: User, accountViewModel: AccountViewModel, navControlle
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||
if (account?.isAcceptable(user) == false) {
|
||||
if (account?.isHidden(user) == false) {
|
||||
ShowUserButton {
|
||||
account.showUser(user.pubkeyHex)
|
||||
LocalPreferences(ctx).saveToEncryptedStorage(account)
|
||||
|
@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.screen
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@ -39,6 +40,7 @@ import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.components.RichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.note.BlankNote
|
||||
import com.vitorpamplona.amethyst.ui.note.HiddenNote
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.ReactionsRow
|
||||
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
||||
@ -157,11 +159,16 @@ fun NoteMaster(baseNote: Note, accountViewModel: AccountViewModel, navController
|
||||
val noteState by baseNote.live.observeAsState()
|
||||
val note = noteState?.note
|
||||
|
||||
val noteReportsState by baseNote.liveReports.observeAsState()
|
||||
val noteForReports = noteReportsState?.note ?: return
|
||||
|
||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||
val account = accountState?.account ?: return
|
||||
|
||||
if (note?.event == null) {
|
||||
BlankNote()
|
||||
} else if (!account.isAcceptable(noteForReports)) {
|
||||
HiddenNote()
|
||||
} else {
|
||||
val authorState by note.author!!.live.observeAsState()
|
||||
val author = authorState?.user
|
||||
|
@ -9,6 +9,7 @@ import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.AccountState
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||
|
||||
class AccountViewModel(private val account: Account): ViewModel() {
|
||||
val accountLiveData: LiveData<AccountState> = account.live.map { it }
|
||||
@ -17,8 +18,12 @@ class AccountViewModel(private val account: Account): ViewModel() {
|
||||
account.reactTo(note)
|
||||
}
|
||||
|
||||
fun report(note: Note) {
|
||||
account.report(note)
|
||||
fun report(note: Note, type: ReportEvent.ReportType) {
|
||||
account.report(note, type)
|
||||
}
|
||||
|
||||
fun report(user: User, type: ReportEvent.ReportType) {
|
||||
account.report(user, type)
|
||||
}
|
||||
|
||||
fun boost(note: Note) {
|
||||
|
@ -76,6 +76,7 @@ import com.vitorpamplona.amethyst.model.toNote
|
||||
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrUserProfileFollowersDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrUserProfileFollowsDataSource
|
||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView
|
||||
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
||||
@ -266,7 +267,7 @@ private fun ProfileHeader(
|
||||
if (accountUser == user) {
|
||||
EditButton(account)
|
||||
} else {
|
||||
if (!account.isAcceptable(user)) {
|
||||
if (!account.isHidden(user)) {
|
||||
ShowUserButton {
|
||||
account.showUser(user.pubkeyHex)
|
||||
LocalPreferences(ctx).saveToEncryptedStorage(account)
|
||||
@ -507,21 +508,53 @@ fun UserProfileDropDownMenu(user: User, popupExpanded: Boolean, onDismiss: () ->
|
||||
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(user.pubkey.toNpub() ?: "")); onDismiss() }) {
|
||||
Text("Copy User ID")
|
||||
}
|
||||
Divider()
|
||||
if (!account.isAcceptable(user)) {
|
||||
DropdownMenuItem(onClick = {
|
||||
user.let {
|
||||
accountViewModel.show(
|
||||
it,
|
||||
context
|
||||
)
|
||||
}; onDismiss()
|
||||
}) {
|
||||
Text("Unblock User")
|
||||
|
||||
if ( account.userProfile() != user) {
|
||||
Divider()
|
||||
if (!account.isHidden(user)) {
|
||||
DropdownMenuItem(onClick = {
|
||||
user.let {
|
||||
accountViewModel.show(
|
||||
it,
|
||||
context
|
||||
)
|
||||
}; onDismiss()
|
||||
}) {
|
||||
Text("Unblock User")
|
||||
}
|
||||
} else {
|
||||
DropdownMenuItem(onClick = { user.let { accountViewModel.hide(it, context) }; onDismiss() }) {
|
||||
Text("Hide User")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DropdownMenuItem(onClick = { user.let { accountViewModel.hide(it, context) }; onDismiss() }) {
|
||||
Text("Block User")
|
||||
Divider()
|
||||
DropdownMenuItem(onClick = {
|
||||
accountViewModel.report(user, ReportEvent.ReportType.SPAM);
|
||||
user.let { accountViewModel.hide(it, context) }
|
||||
onDismiss()
|
||||
}) {
|
||||
Text("Report Spam / Scam")
|
||||
}
|
||||
DropdownMenuItem(onClick = {
|
||||
accountViewModel.report(user, ReportEvent.ReportType.IMPERSONATION);
|
||||
user.let { accountViewModel.hide(it, context) }
|
||||
onDismiss()
|
||||
}) {
|
||||
Text("Report Impersonation")
|
||||
}
|
||||
DropdownMenuItem(onClick = {
|
||||
accountViewModel.report(user, ReportEvent.ReportType.EXPLICIT);
|
||||
user.let { accountViewModel.hide(it, context) }
|
||||
onDismiss()
|
||||
}) {
|
||||
Text("Report Explicit Content")
|
||||
}
|
||||
DropdownMenuItem(onClick = {
|
||||
accountViewModel.report(user, ReportEvent.ReportType.ILLEGAL);
|
||||
user.let { accountViewModel.hide(it, context) }
|
||||
onDismiss()
|
||||
}) {
|
||||
Text("Report Illegal Behaviour")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user