Merge branch 'main' into main
This commit is contained in:
+2
-2
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId "com.vitorpamplona.amethyst"
|
applicationId "com.vitorpamplona.amethyst"
|
||||||
minSdk 26
|
minSdk 26
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 77
|
versionCode 79
|
||||||
versionName "0.20.3"
|
versionName "0.20.5"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
|||||||
@@ -44,12 +44,10 @@ class NotificationLiveData(val cache: NotificationCache): LiveData<NotificationS
|
|||||||
// Refreshes observers in batches.
|
// Refreshes observers in batches.
|
||||||
var handlerWaiting = AtomicBoolean()
|
var handlerWaiting = AtomicBoolean()
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun invalidateData() {
|
fun invalidateData() {
|
||||||
if (!hasActiveObservers()) return
|
if (!hasActiveObservers()) return
|
||||||
if (handlerWaiting.getAndSet(true)) return
|
if (handlerWaiting.getAndSet(true)) return
|
||||||
|
|
||||||
handlerWaiting.set(true)
|
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ object ServiceManager {
|
|||||||
NostrAccountDataSource.account = myAccount
|
NostrAccountDataSource.account = myAccount
|
||||||
NostrHomeDataSource.account = myAccount
|
NostrHomeDataSource.account = myAccount
|
||||||
NostrChatroomListDataSource.account = myAccount
|
NostrChatroomListDataSource.account = myAccount
|
||||||
NostrGlobalDataSource.account = myAccount
|
|
||||||
|
|
||||||
// Notification Elements
|
// Notification Elements
|
||||||
NostrAccountDataSource.start()
|
NostrAccountDataSource.start()
|
||||||
|
|||||||
@@ -605,11 +605,9 @@ class Account(
|
|||||||
class AccountLiveData(private val account: Account): LiveData<AccountState>(AccountState(account)) {
|
class AccountLiveData(private val account: Account): LiveData<AccountState>(AccountState(account)) {
|
||||||
var handlerWaiting = AtomicBoolean()
|
var handlerWaiting = AtomicBoolean()
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun invalidateData() {
|
fun invalidateData() {
|
||||||
if (handlerWaiting.getAndSet(true)) return
|
if (handlerWaiting.getAndSet(true)) return
|
||||||
|
|
||||||
handlerWaiting.set(true)
|
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Default)
|
val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -67,12 +67,10 @@ class AntiSpamLiveData(val cache: AntiSpamFilter): LiveData<AntiSpamState>(AntiS
|
|||||||
// Refreshes observers in batches.
|
// Refreshes observers in batches.
|
||||||
var handlerWaiting = AtomicBoolean()
|
var handlerWaiting = AtomicBoolean()
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun invalidateData() {
|
fun invalidateData() {
|
||||||
if (!hasActiveObservers()) return
|
if (!hasActiveObservers()) return
|
||||||
if (handlerWaiting.getAndSet(true)) return
|
if (handlerWaiting.getAndSet(true)) return
|
||||||
|
|
||||||
handlerWaiting.set(true)
|
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ class Channel(val idHex: String) {
|
|||||||
return info.name ?: idDisplayNote()
|
return info.name ?: idDisplayNote()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun addNote(note: Note) {
|
fun addNote(note: Note) {
|
||||||
notes[note.idHex] = note
|
notes[note.idHex] = note
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -408,8 +408,8 @@ object LocalCache {
|
|||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
val mentions = event.reportedAuthor.mapNotNull { checkGetOrCreateUser(it) }
|
val mentions = event.reportedAuthor.mapNotNull { checkGetOrCreateUser(it.key) }
|
||||||
val repliesTo = event.reportedPost.mapNotNull { checkGetOrCreateNote(it) }
|
val repliesTo = event.reportedPost.mapNotNull { checkGetOrCreateNote(it.key) }
|
||||||
|
|
||||||
note.loadEvent(event, author, mentions, repliesTo)
|
note.loadEvent(event, author, mentions, repliesTo)
|
||||||
|
|
||||||
@@ -735,12 +735,10 @@ class LocalCacheLiveData(val cache: LocalCache): LiveData<LocalCacheState>(Local
|
|||||||
// Refreshes observers in batches.
|
// Refreshes observers in batches.
|
||||||
var handlerWaiting = AtomicBoolean()
|
var handlerWaiting = AtomicBoolean()
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun invalidateData() {
|
fun invalidateData() {
|
||||||
if (!hasActiveObservers()) return
|
if (!hasActiveObservers()) return
|
||||||
if (handlerWaiting.getAndSet(true)) return
|
if (handlerWaiting.getAndSet(true)) return
|
||||||
|
|
||||||
handlerWaiting.set(true)
|
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ import java.util.regex.Pattern
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.NonCancellable
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import nostr.postr.events.Event
|
import nostr.postr.events.Event
|
||||||
|
|
||||||
val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]")
|
val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]")
|
||||||
@@ -318,17 +320,20 @@ class NoteLiveData(val note: Note): LiveData<NoteState>(NoteState(note)) {
|
|||||||
// Refreshes observers in batches.
|
// Refreshes observers in batches.
|
||||||
var handlerWaiting = AtomicBoolean()
|
var handlerWaiting = AtomicBoolean()
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun invalidateData() {
|
fun invalidateData() {
|
||||||
if (!hasActiveObservers()) return
|
if (!hasActiveObservers()) return
|
||||||
if (handlerWaiting.getAndSet(true)) return
|
if (handlerWaiting.getAndSet(true)) return
|
||||||
|
|
||||||
handlerWaiting.set(true)
|
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
delay(100)
|
try {
|
||||||
refresh()
|
delay(100)
|
||||||
handlerWaiting.set(false)
|
refresh()
|
||||||
|
} finally {
|
||||||
|
withContext(NonCancellable) {
|
||||||
|
handlerWaiting.set(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ class User(val pubkeyHex: String) {
|
|||||||
|
|
||||||
fun hasReport(loggedIn: User, type: ReportEvent.ReportType): Boolean {
|
fun hasReport(loggedIn: User, type: ReportEvent.ReportType): Boolean {
|
||||||
return reports[loggedIn]?.firstOrNull() {
|
return reports[loggedIn]?.firstOrNull() {
|
||||||
it.event is ReportEvent && (it.event as ReportEvent).reportType.contains(type)
|
it.event is ReportEvent && (it.event as ReportEvent).reportedAuthor.any { it.reportType == type }
|
||||||
} != null
|
} != null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,11 +379,9 @@ class UserLiveData(val user: User): LiveData<UserState>(UserState(user)) {
|
|||||||
// Refreshes observers in batches.
|
// Refreshes observers in batches.
|
||||||
var handlerWaiting = AtomicBoolean()
|
var handlerWaiting = AtomicBoolean()
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun invalidateData() {
|
fun invalidateData() {
|
||||||
if (handlerWaiting.getAndSet(true)) return
|
if (handlerWaiting.getAndSet(true)) return
|
||||||
|
|
||||||
handlerWaiting.set(true)
|
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -17,11 +17,14 @@ import com.vitorpamplona.amethyst.service.relays.Subscription
|
|||||||
import com.vitorpamplona.amethyst.service.relays.hasValidSignature
|
import com.vitorpamplona.amethyst.service.relays.hasValidSignature
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.NonCancellable
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import nostr.postr.events.ContactListEvent
|
import nostr.postr.events.ContactListEvent
|
||||||
import nostr.postr.events.DeletionEvent
|
import nostr.postr.events.DeletionEvent
|
||||||
import nostr.postr.events.Event
|
import nostr.postr.events.Event
|
||||||
@@ -42,6 +45,7 @@ abstract class NostrDataSource(val debugName: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val clientListener = object : Client.Listener() {
|
private val clientListener = object : Client.Listener() {
|
||||||
override fun onEvent(event: Event, subscriptionId: String, relay: Relay) {
|
override fun onEvent(event: Event, subscriptionId: String, relay: Relay) {
|
||||||
if (subscriptionId in subscriptions.keys) {
|
if (subscriptionId in subscriptions.keys) {
|
||||||
@@ -91,7 +95,6 @@ abstract class NostrDataSource(val debugName: String) {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,10 +126,12 @@ abstract class NostrDataSource(val debugName: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
open fun start() {
|
open fun start() {
|
||||||
|
println("DataSource: ${this.javaClass.simpleName} Start")
|
||||||
resetFilters()
|
resetFilters()
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun stop() {
|
open fun stop() {
|
||||||
|
println("DataSource: ${this.javaClass.simpleName} Stop")
|
||||||
subscriptions.values.forEach { channel ->
|
subscriptions.values.forEach { channel ->
|
||||||
Client.close(channel.id)
|
Client.close(channel.id)
|
||||||
channel.typedFilters = null
|
channel.typedFilters = null
|
||||||
@@ -144,17 +149,24 @@ abstract class NostrDataSource(val debugName: String) {
|
|||||||
subscriptions = subscriptions.minus(subscription.id)
|
subscriptions = subscriptions.minus(subscription.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
var handlerWaiting = false
|
// Refreshes observers in batches.
|
||||||
@Synchronized
|
var handlerWaiting = AtomicBoolean()
|
||||||
fun invalidateFilters() {
|
|
||||||
if (handlerWaiting) return
|
fun invalidateFilters() {
|
||||||
|
if (handlerWaiting.getAndSet(true)) return
|
||||||
|
|
||||||
|
println("DataSource: ${this.javaClass.simpleName} InvalidateFilters")
|
||||||
|
|
||||||
handlerWaiting = true
|
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.IO)
|
val scope = CoroutineScope(Job() + Dispatchers.IO)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
delay(200)
|
try {
|
||||||
resetFiltersSuspend()
|
delay(200)
|
||||||
handlerWaiting = false
|
resetFiltersSuspend()
|
||||||
|
} finally {
|
||||||
|
withContext(NonCancellable) {
|
||||||
|
handlerWaiting.set(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import nostr.postr.JsonFilter
|
|||||||
import nostr.postr.events.TextNoteEvent
|
import nostr.postr.events.TextNoteEvent
|
||||||
|
|
||||||
object NostrGlobalDataSource: NostrDataSource("GlobalFeed") {
|
object NostrGlobalDataSource: NostrDataSource("GlobalFeed") {
|
||||||
lateinit var account: Account
|
|
||||||
fun createGlobalFilter() = TypedFilter(
|
fun createGlobalFilter() = TypedFilter(
|
||||||
types = setOf(FeedType.GLOBAL),
|
types = setOf(FeedType.GLOBAL),
|
||||||
filter = JsonFilter(
|
filter = JsonFilter(
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import nostr.postr.Utils
|
|||||||
import nostr.postr.events.Event
|
import nostr.postr.events.Event
|
||||||
import nostr.postr.toHex
|
import nostr.postr.toHex
|
||||||
|
|
||||||
|
data class ReportedKey(val key: String, val reportType: ReportEvent.ReportType)
|
||||||
|
|
||||||
// NIP 56 event.
|
// NIP 56 event.
|
||||||
class ReportEvent (
|
class ReportEvent (
|
||||||
id: ByteArray,
|
id: ByteArray,
|
||||||
@@ -15,14 +17,37 @@ class ReportEvent (
|
|||||||
sig: ByteArray
|
sig: ByteArray
|
||||||
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
): Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
|
||||||
@Transient val reportType: List<ReportType>
|
@Transient val reportedPost: List<ReportedKey>
|
||||||
@Transient val reportedPost: List<String>
|
@Transient val reportedAuthor: List<ReportedKey>
|
||||||
@Transient val reportedAuthor: List<String>
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
reportType = tags.filter { it.firstOrNull() == "report" }.mapNotNull { it.getOrNull(1) }.map { ReportType.valueOf(it.toUpperCase()) }
|
// Works with old and new structures for report.
|
||||||
reportedPost = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
|
||||||
reportedAuthor = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
var reportType = tags.filter { it.firstOrNull() == "report" }.mapNotNull { it.getOrNull(1) }.map { ReportType.valueOf(it.toUpperCase()) }.firstOrNull()
|
||||||
|
if (reportType == null) {
|
||||||
|
reportType = tags.mapNotNull { it.getOrNull(2) }.map { ReportType.valueOf(it.toUpperCase()) }.firstOrNull()
|
||||||
|
}
|
||||||
|
if (reportType == null) {
|
||||||
|
reportType = ReportType.SPAM
|
||||||
|
}
|
||||||
|
|
||||||
|
reportedPost = tags
|
||||||
|
.filter { it.firstOrNull() == "e" && it.getOrNull(1) != null }
|
||||||
|
.map {
|
||||||
|
ReportedKey(
|
||||||
|
it[1],
|
||||||
|
it.getOrNull(2)?.toUpperCase()?.let { it1 -> ReportType.valueOf(it1) }?: reportType
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
reportedAuthor = tags
|
||||||
|
.filter { it.firstOrNull() == "p" && it.getOrNull(1) != null }
|
||||||
|
.map {
|
||||||
|
ReportedKey(
|
||||||
|
it[1],
|
||||||
|
it.getOrNull(2)?.toUpperCase()?.let { it1 -> ReportType.valueOf(it1) }?: reportType
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -31,12 +56,11 @@ class ReportEvent (
|
|||||||
fun create(reportedPost: Event, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent {
|
fun create(reportedPost: Event, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent {
|
||||||
val content = ""
|
val content = ""
|
||||||
|
|
||||||
val reportTypeTag = listOf("report", type.name.toLowerCase())
|
val reportPostTag = listOf("e", reportedPost.id.toHex(), type.name.toLowerCase())
|
||||||
val reportPostTag = listOf("e", reportedPost.id.toHex())
|
val reportAuthorTag = listOf("p", reportedPost.pubKey.toHex(), type.name.toLowerCase())
|
||||||
val reportAuthorTag = listOf("p", reportedPost.pubKey.toHex())
|
|
||||||
|
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey)
|
||||||
val tags:List<List<String>> = listOf(reportTypeTag, reportPostTag, reportAuthorTag)
|
val tags:List<List<String>> = listOf(reportPostTag, reportAuthorTag)
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
return ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
@@ -45,11 +69,10 @@ class ReportEvent (
|
|||||||
fun create(reportedUser: String, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent {
|
fun create(reportedUser: String, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent {
|
||||||
val content = ""
|
val content = ""
|
||||||
|
|
||||||
val reportTypeTag = listOf("report", type.name.toLowerCase())
|
val reportAuthorTag = listOf("p", reportedUser, type.name.toLowerCase())
|
||||||
val reportAuthorTag = listOf("p", reportedUser)
|
|
||||||
|
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey)
|
||||||
val tags:List<List<String>> = listOf(reportTypeTag, reportAuthorTag)
|
val tags:List<List<String>> = listOf(reportAuthorTag)
|
||||||
val id = generateId(pubKey, createdAt, kind, tags, content)
|
val id = generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey)
|
val sig = Utils.sign(id, privateKey)
|
||||||
return ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
return ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
@@ -57,11 +80,11 @@ class ReportEvent (
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class ReportType() {
|
enum class ReportType() {
|
||||||
EXPLICIT,
|
EXPLICIT, // Not used anymore.
|
||||||
ILLEGAL,
|
ILLEGAL,
|
||||||
SPAM,
|
SPAM,
|
||||||
IMPERSONATION,
|
IMPERSONATION,
|
||||||
NUDITY,
|
NUDITY,
|
||||||
PROFANITY
|
PROFANITY,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,6 @@ object RelayPool: Relay.Listener {
|
|||||||
fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay)
|
fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
override fun onEvent(relay: Relay, subscriptionId: String, event: Event) {
|
override fun onEvent(relay: Relay, subscriptionId: String, event: Event) {
|
||||||
listeners.forEach { it.onEvent(event, subscriptionId, relay) }
|
listeners.forEach { it.onEvent(event, subscriptionId, relay) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,19 +23,6 @@ object NotificationFeedFilter: FeedFilter<Note>() {
|
|||||||
&& it.event !is ChannelMetadataEvent
|
&& it.event !is ChannelMetadataEvent
|
||||||
&& it.event !is LnZapRequestEvent
|
&& it.event !is LnZapRequestEvent
|
||||||
}
|
}
|
||||||
.filter {
|
|
||||||
it.event !is TextNoteEvent
|
|
||||||
||
|
|
||||||
(
|
|
||||||
it.event is TextNoteEvent
|
|
||||||
&&
|
|
||||||
(
|
|
||||||
it.replyTo?.lastOrNull()?.author == account.userProfile()
|
|
||||||
||
|
|
||||||
account.userProfile() in it.directlyCiteUsers()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.filter {
|
.filter {
|
||||||
it.event !is ReactionEvent
|
it.event !is ReactionEvent
|
||||||
||
|
||
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, modifier: Modifier = Modifier, r
|
|||||||
|
|
||||||
Row(Modifier.fillMaxWidth()) {
|
Row(Modifier.fillMaxWidth()) {
|
||||||
Box(modifier = Modifier
|
Box(modifier = Modifier
|
||||||
.width(55.dp)
|
.width(65.dp)
|
||||||
.padding(0.dp)) {
|
.padding(0.dp)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,8 @@ fun ObserveDisplayNip05Status(baseUser: User) {
|
|||||||
|
|
||||||
user.nip05()?.let { nip05 ->
|
user.nip05()?.let { nip05 ->
|
||||||
if (nip05.split("@").size == 2) {
|
if (nip05.split("@").size == 2) {
|
||||||
val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex)
|
|
||||||
|
val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex)
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
if (nip05.split("@")[0] != "_")
|
if (nip05.split("@")[0] != "_")
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Following
|
import com.vitorpamplona.amethyst.ui.theme.Following
|
||||||
import kotlin.time.ExperimentalTime
|
import kotlin.time.ExperimentalTime
|
||||||
|
import nostr.postr.events.PrivateDmEvent
|
||||||
import nostr.postr.events.TextNoteEvent
|
import nostr.postr.events.TextNoteEvent
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@@ -319,25 +320,26 @@ fun NoteCompose(
|
|||||||
val reportType = noteEvent.reportType.map {
|
val reportType = noteEvent.reportType.map {
|
||||||
when (it) {
|
when (it) {
|
||||||
ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content)
|
ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content)
|
||||||
|
ReportEvent.ReportType.NUDITY -> stringResource(R.string.nudity)
|
||||||
|
ReportEvent.ReportType.PROFANITY -> stringResource(R.string.profanity_hateful_speech)
|
||||||
ReportEvent.ReportType.SPAM -> stringResource(R.string.spam)
|
ReportEvent.ReportType.SPAM -> stringResource(R.string.spam)
|
||||||
ReportEvent.ReportType.IMPERSONATION -> stringResource(R.string.impersonation)
|
ReportEvent.ReportType.IMPERSONATION -> stringResource(R.string.impersonation)
|
||||||
ReportEvent.ReportType.ILLEGAL -> stringResource(R.string.illegal_behavior)
|
ReportEvent.ReportType.ILLEGAL -> stringResource(R.string.illegal_behavior)
|
||||||
ReportEvent.ReportType.NUDITY -> stringResource(R.string.nudity)
|
|
||||||
ReportEvent.ReportType.PROFANITY -> stringResource(R.string.profanity_hateful_speech)
|
|
||||||
else -> stringResource(R.string.unknown)
|
else -> stringResource(R.string.unknown)
|
||||||
}
|
}
|
||||||
}.joinToString(", ")
|
}.toSet().joinToString(", ")
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = reportType
|
text = reportType
|
||||||
)
|
)
|
||||||
|
|
||||||
Divider(
|
Divider(
|
||||||
modifier = Modifier.padding(top = 10.dp),
|
modifier = Modifier.padding(top = 40.dp),
|
||||||
thickness = 0.25.dp
|
thickness = 0.25.dp
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val eventContent = noteEvent.content
|
val eventContent = accountViewModel.decrypt(note)
|
||||||
|
|
||||||
val canPreview = note.author == account.userProfile()
|
val canPreview = note.author == account.userProfile()
|
||||||
|| (note.author?.let { account.userProfile().isFollowing(it) } ?: true )
|
|| (note.author?.let { account.userProfile().isFollowing(it) } ?: true )
|
||||||
|| !noteForReports.hasAnyReports()
|
|| !noteForReports.hasAnyReports()
|
||||||
@@ -605,6 +607,13 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
|
|||||||
}) {
|
}) {
|
||||||
Text(stringResource(R.string.report_spam_scam))
|
Text(stringResource(R.string.report_spam_scam))
|
||||||
}
|
}
|
||||||
|
DropdownMenuItem(onClick = {
|
||||||
|
accountViewModel.report(note, ReportEvent.ReportType.PROFANITY);
|
||||||
|
note.author?.let { accountViewModel.hide(it, context) }
|
||||||
|
onDismiss()
|
||||||
|
}) {
|
||||||
|
Text("Report Hateful Speech")
|
||||||
|
}
|
||||||
DropdownMenuItem(onClick = {
|
DropdownMenuItem(onClick = {
|
||||||
accountViewModel.report(note, ReportEvent.ReportType.IMPERSONATION);
|
accountViewModel.report(note, ReportEvent.ReportType.IMPERSONATION);
|
||||||
note.author?.let { accountViewModel.hide(it, context) }
|
note.author?.let { accountViewModel.hide(it, context) }
|
||||||
@@ -613,11 +622,11 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
|
|||||||
Text(stringResource(R.string.report_impersonation))
|
Text(stringResource(R.string.report_impersonation))
|
||||||
}
|
}
|
||||||
DropdownMenuItem(onClick = {
|
DropdownMenuItem(onClick = {
|
||||||
accountViewModel.report(note, ReportEvent.ReportType.EXPLICIT);
|
accountViewModel.report(note, ReportEvent.ReportType.NUDITY);
|
||||||
note.author?.let { accountViewModel.hide(it, context) }
|
note.author?.let { accountViewModel.hide(it, context) }
|
||||||
onDismiss()
|
onDismiss()
|
||||||
}) {
|
}) {
|
||||||
Text(stringResource(R.string.report_explicit_content))
|
Text(stringResource(R.string.report_nudity_porn))
|
||||||
}
|
}
|
||||||
DropdownMenuItem(onClick = {
|
DropdownMenuItem(onClick = {
|
||||||
accountViewModel.report(note, ReportEvent.ReportType.ILLEGAL);
|
accountViewModel.report(note, ReportEvent.ReportType.ILLEGAL);
|
||||||
|
|||||||
@@ -129,11 +129,9 @@ open class CardFeedViewModel(val dataSource: FeedFilter<Note>): ViewModel() {
|
|||||||
|
|
||||||
var handlerWaiting = AtomicBoolean()
|
var handlerWaiting = AtomicBoolean()
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun invalidateData() {
|
private fun invalidateData() {
|
||||||
if (handlerWaiting.getAndSet(true)) return
|
if (handlerWaiting.getAndSet(true)) return
|
||||||
|
|
||||||
handlerWaiting.set(true)
|
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Default)
|
val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -87,11 +87,10 @@ abstract class FeedViewModel(val localFilter: FeedFilter<Note>): ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var handlerWaiting = AtomicBoolean()
|
private var handlerWaiting = AtomicBoolean()
|
||||||
@Synchronized
|
|
||||||
fun invalidateData() {
|
fun invalidateData() {
|
||||||
if (handlerWaiting.getAndSet(true)) return
|
if (handlerWaiting.getAndSet(true)) return
|
||||||
|
|
||||||
handlerWaiting.set(true)
|
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Default)
|
val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -61,11 +61,9 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter<Pair<Note, Note>>): Vie
|
|||||||
|
|
||||||
var handlerWaiting = AtomicBoolean()
|
var handlerWaiting = AtomicBoolean()
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun invalidateData() {
|
private fun invalidateData() {
|
||||||
if (handlerWaiting.getAndSet(true)) return
|
if (handlerWaiting.getAndSet(true)) return
|
||||||
|
|
||||||
handlerWaiting.set(true)
|
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Default)
|
val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -83,11 +83,9 @@ class RelayFeedViewModel: ViewModel() {
|
|||||||
|
|
||||||
var handlerWaiting = AtomicBoolean()
|
var handlerWaiting = AtomicBoolean()
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun invalidateData() {
|
private fun invalidateData() {
|
||||||
if (handlerWaiting.getAndSet(true)) return
|
if (handlerWaiting.getAndSet(true)) return
|
||||||
|
|
||||||
handlerWaiting.set(true)
|
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Default)
|
val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -65,11 +65,9 @@ open class UserFeedViewModel(val dataSource: FeedFilter<User>): ViewModel() {
|
|||||||
|
|
||||||
var handlerWaiting = AtomicBoolean()
|
var handlerWaiting = AtomicBoolean()
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun invalidateData() {
|
private fun invalidateData() {
|
||||||
if (handlerWaiting.getAndSet(true)) return
|
if (handlerWaiting.getAndSet(true)) return
|
||||||
|
|
||||||
handlerWaiting.set(true)
|
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Default)
|
val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
|
|||||||
+3
-43
@@ -89,31 +89,11 @@ fun TabKnown(accountViewModel: AccountViewModel, navController: NavController) {
|
|||||||
ChatroomListKnownFeedFilter.account = account
|
ChatroomListKnownFeedFilter.account = account
|
||||||
val feedViewModel: NostrChatroomListKnownFeedViewModel = viewModel()
|
val feedViewModel: NostrChatroomListKnownFeedViewModel = viewModel()
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(accountViewModel) {
|
||||||
NostrChatroomListDataSource.resetFilters()
|
NostrChatroomListDataSource.resetFilters()
|
||||||
feedViewModel.invalidateData()
|
feedViewModel.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
|
||||||
DisposableEffect(accountViewModel) {
|
|
||||||
val observer = LifecycleEventObserver { source, event ->
|
|
||||||
if (event == Lifecycle.Event.ON_RESUME) {
|
|
||||||
println("Chatroom List Start")
|
|
||||||
NostrChatroomListDataSource.start()
|
|
||||||
feedViewModel.invalidateData()
|
|
||||||
}
|
|
||||||
if (event == Lifecycle.Event.ON_PAUSE) {
|
|
||||||
println("Chatroom List Stop")
|
|
||||||
NostrChatroomListDataSource.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lifeCycleOwner.lifecycle.addObserver(observer)
|
|
||||||
onDispose {
|
|
||||||
lifeCycleOwner.lifecycle.removeObserver(observer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(vertical = 0.dp)
|
modifier = Modifier.padding(vertical = 0.dp)
|
||||||
@@ -131,29 +111,9 @@ fun TabNew(accountViewModel: AccountViewModel, navController: NavController) {
|
|||||||
ChatroomListNewFeedFilter.account = account
|
ChatroomListNewFeedFilter.account = account
|
||||||
val feedViewModel: NostrChatroomListNewFeedViewModel = viewModel()
|
val feedViewModel: NostrChatroomListNewFeedViewModel = viewModel()
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(accountViewModel) {
|
||||||
NostrChatroomListDataSource.resetFilters()
|
NostrChatroomListDataSource.resetFilters()
|
||||||
feedViewModel.refresh() // refresh view
|
feedViewModel.invalidateData() // refresh view
|
||||||
}
|
|
||||||
|
|
||||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
|
||||||
DisposableEffect(accountViewModel) {
|
|
||||||
val observer = LifecycleEventObserver { source, event ->
|
|
||||||
if (event == Lifecycle.Event.ON_RESUME) {
|
|
||||||
println("Chatroom List Start")
|
|
||||||
NostrChatroomListDataSource.start()
|
|
||||||
feedViewModel.invalidateData()
|
|
||||||
}
|
|
||||||
if (event == Lifecycle.Event.ON_PAUSE) {
|
|
||||||
println("Chatroom List Stop")
|
|
||||||
NostrChatroomListDataSource.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lifeCycleOwner.lifecycle.addObserver(observer)
|
|
||||||
onDispose {
|
|
||||||
lifeCycleOwner.lifecycle.removeObserver(observer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
|
|||||||
@@ -58,27 +58,6 @@ fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController)
|
|||||||
feedViewModelReplies.invalidateData()
|
feedViewModelReplies.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
|
||||||
DisposableEffect(accountViewModel) {
|
|
||||||
val observer = LifecycleEventObserver { source, event ->
|
|
||||||
if (event == Lifecycle.Event.ON_RESUME) {
|
|
||||||
println("Home Start")
|
|
||||||
NostrHomeDataSource.start()
|
|
||||||
feedViewModel.invalidateData()
|
|
||||||
feedViewModelReplies.invalidateData()
|
|
||||||
}
|
|
||||||
if (event == Lifecycle.Event.ON_PAUSE) {
|
|
||||||
println("Home Stop")
|
|
||||||
NostrHomeDataSource.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lifeCycleOwner.lifecycle.addObserver(observer)
|
|
||||||
onDispose {
|
|
||||||
lifeCycleOwner.lifecycle.removeObserver(observer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(vertical = 0.dp)
|
modifier = Modifier.padding(vertical = 0.dp)
|
||||||
|
|||||||
@@ -836,7 +836,6 @@ fun UserProfileDropDownMenu(user: User, popupExpanded: Boolean, onDismiss: () ->
|
|||||||
}) {
|
}) {
|
||||||
Text(stringResource(R.string.report_hateful_speech))
|
Text(stringResource(R.string.report_hateful_speech))
|
||||||
}
|
}
|
||||||
|
|
||||||
DropdownMenuItem(onClick = {
|
DropdownMenuItem(onClick = {
|
||||||
accountViewModel.report(user, ReportEvent.ReportType.IMPERSONATION);
|
accountViewModel.report(user, ReportEvent.ReportType.IMPERSONATION);
|
||||||
user.let { accountViewModel.hide(it, context) }
|
user.let { accountViewModel.hide(it, context) }
|
||||||
|
|||||||
@@ -83,7 +83,6 @@ fun SearchScreen(accountViewModel: AccountViewModel, navController: NavControlle
|
|||||||
val account = accountState?.account ?: return
|
val account = accountState?.account ?: return
|
||||||
|
|
||||||
GlobalFeedFilter.account = account
|
GlobalFeedFilter.account = account
|
||||||
NostrGlobalDataSource.account = account
|
|
||||||
val feedViewModel: NostrGlobalFeedViewModel = viewModel()
|
val feedViewModel: NostrGlobalFeedViewModel = viewModel()
|
||||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user