mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2024-09-29 16:30:49 +00:00
Abolishes the use of Mutable collections inside data models.
This commit is contained in:
parent
ee7120d803
commit
1a3b92a727
@ -34,8 +34,8 @@ val DefaultChannels = setOf(
|
||||
|
||||
class Account(
|
||||
val loggedIn: Persona,
|
||||
val followingChannels: MutableSet<String> = DefaultChannels.toMutableSet(),
|
||||
val hiddenUsers: MutableSet<String> = mutableSetOf()
|
||||
var followingChannels: Set<String> = DefaultChannels.toMutableSet(),
|
||||
var hiddenUsers: Set<String> = mutableSetOf()
|
||||
) {
|
||||
|
||||
fun userProfile(): User {
|
||||
@ -91,7 +91,7 @@ class Account(
|
||||
fun reactTo(note: Note) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
if (note.reactions.firstOrNull { it.author == userProfile() && it.event?.content == "+" } != null) {
|
||||
if (note.hasReacted(userProfile(), "+")) {
|
||||
// has already liked this note
|
||||
return
|
||||
}
|
||||
@ -106,9 +106,7 @@ class Account(
|
||||
fun report(note: Note, type: ReportEvent.ReportType) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
if (
|
||||
note.reactions.firstOrNull { it.author == userProfile() && it.event?.content == "⚠️"} != null
|
||||
) {
|
||||
if (note.hasReacted(userProfile(), "⚠️")) {
|
||||
// has already liked this note
|
||||
return
|
||||
}
|
||||
@ -129,9 +127,7 @@ class Account(
|
||||
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
|
||||
) {
|
||||
if (user.hasReport(userProfile(), type)) {
|
||||
// has already reported this note
|
||||
return
|
||||
}
|
||||
@ -144,11 +140,7 @@ class Account(
|
||||
fun boost(note: Note) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
val currentTime = Date().time / 1000
|
||||
|
||||
if (
|
||||
note.boosts.firstOrNull { it.author == userProfile() && (it?.event?.createdAt ?: 0) > currentTime - (60 * 5)} != null // 5 minute protection
|
||||
) {
|
||||
if (note.hasBoosted(userProfile())) {
|
||||
// has already bosted in the past 5mins
|
||||
return
|
||||
}
|
||||
@ -269,22 +261,22 @@ class Account(
|
||||
}
|
||||
|
||||
fun joinChannel(idHex: String) {
|
||||
followingChannels.add(idHex)
|
||||
followingChannels = followingChannels + idHex
|
||||
invalidateData()
|
||||
}
|
||||
|
||||
fun leaveChannel(idHex: String) {
|
||||
followingChannels.remove(idHex)
|
||||
followingChannels = followingChannels - idHex
|
||||
invalidateData()
|
||||
}
|
||||
|
||||
fun hideUser(pubkeyHex: String) {
|
||||
hiddenUsers.add(pubkeyHex)
|
||||
hiddenUsers = hiddenUsers + pubkeyHex
|
||||
invalidateData()
|
||||
}
|
||||
|
||||
fun showUser(pubkeyHex: String) {
|
||||
hiddenUsers.remove(pubkeyHex)
|
||||
hiddenUsers = hiddenUsers - pubkeyHex
|
||||
invalidateData()
|
||||
}
|
||||
|
||||
@ -304,7 +296,7 @@ class Account(
|
||||
Client.send(event)
|
||||
LocalCache.consume(event)
|
||||
|
||||
followingChannels.add(event.id.toHex())
|
||||
joinChannel(event.id.toHex())
|
||||
}
|
||||
|
||||
fun decryptContent(note: Note): String? {
|
||||
|
@ -105,15 +105,15 @@ object LocalCache {
|
||||
// Already processed this event.
|
||||
if (note.event != null) return
|
||||
|
||||
val mentions = Collections.synchronizedList(event.mentions.map { getOrCreateUser(it) })
|
||||
val replyTo = Collections.synchronizedList(event.replyTos.map { getOrCreateNote(it) }.toMutableList())
|
||||
val mentions = event.mentions.map { getOrCreateUser(it) }
|
||||
val replyTo = event.replyTos.map { getOrCreateNote(it) }
|
||||
|
||||
note.loadEvent(event, author, mentions, replyTo)
|
||||
|
||||
//Log.d("TN", "New Note (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${note.event?.content?.take(100)} ${formattedDateTime(event.createdAt)}")
|
||||
|
||||
// Prepares user's profile view.
|
||||
author.notes.add(note)
|
||||
author.addNote(note)
|
||||
|
||||
// Adds notifications to users.
|
||||
mentions.forEach {
|
||||
@ -183,7 +183,7 @@ object LocalCache {
|
||||
|
||||
//Log.d("PM", "${author.toBestDisplayName()} to ${recipient?.toBestDisplayName()}")
|
||||
|
||||
val repliesTo = event.tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }.map { getOrCreateNote(it) }.toMutableList()
|
||||
val repliesTo = event.tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }.map { getOrCreateNote(it) }
|
||||
val mentions = event.tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }.map { getOrCreateUser(it) }
|
||||
|
||||
note.loadEvent(event, author, mentions, repliesTo)
|
||||
@ -209,13 +209,13 @@ object LocalCache {
|
||||
//Log.d("TN", "New Boost (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
||||
|
||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
||||
val mentions = event.originalAuthor.map { getOrCreateUser(it) }.toList()
|
||||
val repliesTo = event.boostedPost.map { getOrCreateNote(it) }.toMutableList()
|
||||
val mentions = event.originalAuthor.map { getOrCreateUser(it) }
|
||||
val repliesTo = event.boostedPost.map { getOrCreateNote(it) }
|
||||
|
||||
note.loadEvent(event, author, mentions, repliesTo)
|
||||
|
||||
// Prepares user's profile view.
|
||||
author.notes.add(note)
|
||||
author.addNote(note)
|
||||
|
||||
// Adds notifications to users.
|
||||
mentions.forEach {
|
||||
@ -241,7 +241,7 @@ object LocalCache {
|
||||
|
||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
||||
val mentions = event.originalAuthor.map { getOrCreateUser(it) }
|
||||
val repliesTo = event.originalPost.map { getOrCreateNote(it) }.toMutableList()
|
||||
val repliesTo = event.originalPost.map { getOrCreateNote(it) }
|
||||
|
||||
note.loadEvent(event, author, mentions, repliesTo)
|
||||
|
||||
@ -286,7 +286,7 @@ object LocalCache {
|
||||
|
||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
||||
val mentions = event.reportedAuthor.map { getOrCreateUser(it) }
|
||||
val repliesTo = event.reportedPost.map { getOrCreateNote(it) }.toMutableList()
|
||||
val repliesTo = event.reportedPost.map { getOrCreateNote(it) }
|
||||
|
||||
note.loadEvent(event, author, mentions, repliesTo)
|
||||
|
||||
@ -312,7 +312,7 @@ object LocalCache {
|
||||
val note = getOrCreateNote(event.id.toHex())
|
||||
oldChannel.addNote(note)
|
||||
note.channel = oldChannel
|
||||
note.loadEvent(event, author, emptyList(), mutableListOf())
|
||||
note.loadEvent(event, author, emptyList(), emptyList())
|
||||
|
||||
refreshObservers()
|
||||
}
|
||||
@ -334,7 +334,7 @@ object LocalCache {
|
||||
val note = getOrCreateNote(event.id.toHex())
|
||||
oldChannel.addNote(note)
|
||||
note.channel = oldChannel
|
||||
note.loadEvent(event, author, emptyList(), mutableListOf())
|
||||
note.loadEvent(event, author, emptyList(), emptyList())
|
||||
|
||||
refreshObservers()
|
||||
}
|
||||
@ -355,20 +355,10 @@ object LocalCache {
|
||||
if (note.event != null) return
|
||||
|
||||
val author = getOrCreateUser(event.pubKey.toHexKey())
|
||||
val mentions = Collections.synchronizedList(event.mentions.map { getOrCreateUser(it) })
|
||||
val replyTo = Collections.synchronizedList(
|
||||
event.replyTos
|
||||
.mapNotNull {
|
||||
try {
|
||||
getOrCreateNote(it)
|
||||
} catch (e: Exception) {
|
||||
println("Failed to parse Key: $it")
|
||||
null
|
||||
}
|
||||
}
|
||||
.filter { it.event !is ChannelCreateEvent }
|
||||
.toMutableList()
|
||||
)
|
||||
val mentions = event.mentions.map { getOrCreateUser(it) }
|
||||
val replyTo = event.replyTos
|
||||
.map { getOrCreateNote(it) }
|
||||
.filter { it.event !is ChannelCreateEvent }
|
||||
|
||||
note.channel = channel
|
||||
note.loadEvent(event, author, mentions, replyTo)
|
||||
|
@ -16,6 +16,7 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||
import java.util.Date
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
import nostr.postr.events.Event
|
||||
@ -33,19 +34,23 @@ class Note(val idHex: String) {
|
||||
var event: Event? = null
|
||||
var author: User? = null
|
||||
var mentions: List<User>? = null
|
||||
var replyTo: MutableList<Note>? = null
|
||||
var replyTo: List<Note>? = null
|
||||
|
||||
// These fields are updated every time an event related to this note is received.
|
||||
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 replies = setOf<Note>()
|
||||
private set
|
||||
var reactions = setOf<Note>()
|
||||
private set
|
||||
var boosts = setOf<Note>()
|
||||
private set
|
||||
var reports = setOf<Note>()
|
||||
private set
|
||||
|
||||
var channel: Channel? = null
|
||||
|
||||
var lastReactionsDownloadTime: Long? = null
|
||||
|
||||
fun loadEvent(event: Event, author: User, mentions: List<User>, replyTo: MutableList<Note>) {
|
||||
fun loadEvent(event: Event, author: User, mentions: List<User>, replyTo: List<Note>) {
|
||||
this.event = event
|
||||
this.author = author
|
||||
this.mentions = mentions
|
||||
@ -82,47 +87,47 @@ class Note(val idHex: String) {
|
||||
}
|
||||
|
||||
fun addReply(note: Note) {
|
||||
if (replies.add(note))
|
||||
if (note !in replies) {
|
||||
replies = replies + note
|
||||
liveReplies.invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
fun addBoost(note: Note) {
|
||||
if (boosts.add(note))
|
||||
if (note !in boosts) {
|
||||
boosts = boosts + note
|
||||
liveBoosts.invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
fun addReaction(note: Note) {
|
||||
if (reactions.add(note))
|
||||
if (note !in reactions) {
|
||||
reactions = reactions + note
|
||||
liveReactions.invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
fun addReport(note: Note) {
|
||||
if (reports.add(note))
|
||||
if (note !in reports) {
|
||||
reports = reports + note
|
||||
liveReports.invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
fun isReactedBy(user: User): Boolean {
|
||||
return synchronized(reactions) {
|
||||
reactions.any { it.author == user }
|
||||
}
|
||||
return reactions.any { it.author == user }
|
||||
}
|
||||
|
||||
fun isBoostedBy(user: User): Boolean {
|
||||
return synchronized(boosts) {
|
||||
boosts.any { it.author == user }
|
||||
}
|
||||
return boosts.any { it.author == user }
|
||||
}
|
||||
|
||||
fun reportsBy(user: User): List<Note> {
|
||||
return synchronized(reports) {
|
||||
reports.filter { it.author == user }
|
||||
}
|
||||
return reports.filter { it.author == user }
|
||||
}
|
||||
|
||||
fun reportsBy(users: Set<User>): List<Note> {
|
||||
return synchronized(reports) {
|
||||
reports.filter { it.author in users }
|
||||
}
|
||||
return reports.filter { it.author in users }
|
||||
}
|
||||
|
||||
fun directlyCiteUsers(): Set<User> {
|
||||
@ -148,6 +153,19 @@ class Note(val idHex: String) {
|
||||
|| (event is RepostEvent && replyTo?.lastOrNull()?.directlyCites(userProfile) == true)
|
||||
}
|
||||
|
||||
fun isNewThread(): Boolean {
|
||||
return event is RepostEvent || replyTo == null || replyTo?.size == 0
|
||||
}
|
||||
|
||||
fun hasReacted(loggedIn: User, content: String): Boolean {
|
||||
return reactions.firstOrNull { it.author == loggedIn && it.event?.content == content } != null
|
||||
}
|
||||
|
||||
fun hasBoosted(loggedIn: User): Boolean {
|
||||
val currentTime = Date().time / 1000
|
||||
return boosts.firstOrNull { it.author == loggedIn && (it.event?.createdAt ?: 0) > currentTime - (60 * 5)} != null // 5 minute protection
|
||||
}
|
||||
|
||||
// Observers line up here.
|
||||
val live: NoteLiveData = NoteLiveData(this)
|
||||
|
||||
|
@ -14,8 +14,10 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object UrlCachedPreviewer {
|
||||
val cache = ConcurrentHashMap<String, UrlInfoItem>()
|
||||
val failures = ConcurrentHashMap<String, Throwable>()
|
||||
var cache = mapOf<String, UrlInfoItem>()
|
||||
private set
|
||||
var failures = mapOf<String, Throwable>()
|
||||
private set
|
||||
|
||||
fun previewInfo(url: String, callback: IUrlPreviewCallback? = null) {
|
||||
cache[url]?.let {
|
||||
@ -32,12 +34,12 @@ object UrlCachedPreviewer {
|
||||
scope.launch {
|
||||
BahaUrlPreview(url, object : IUrlPreviewCallback {
|
||||
override fun onComplete(urlInfo: UrlInfoItem) {
|
||||
cache.put(url, urlInfo)
|
||||
cache = cache + Pair(url, urlInfo)
|
||||
callback?.onComplete(urlInfo)
|
||||
}
|
||||
|
||||
override fun onFailed(throwable: Throwable) {
|
||||
failures.put(url, throwable)
|
||||
failures = failures + Pair(url, throwable)
|
||||
callback?.onFailed(throwable)
|
||||
}
|
||||
}).fetchUrlPreview()
|
||||
|
@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.model
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||
import com.vitorpamplona.amethyst.service.relays.Client
|
||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||
@ -30,18 +31,27 @@ class User(val pubkeyHex: String) {
|
||||
var latestContactList: ContactListEvent? = null
|
||||
var latestMetadata: MetadataEvent? = null
|
||||
|
||||
val notes = Collections.synchronizedSet(mutableSetOf<Note>())
|
||||
val follows = Collections.synchronizedSet(mutableSetOf<User>())
|
||||
var follows = setOf<User>()
|
||||
private set
|
||||
var followers = setOf<User>()
|
||||
private set
|
||||
|
||||
val taggedPosts = Collections.synchronizedSet(mutableSetOf<Note>())
|
||||
var notes = setOf<Note>()
|
||||
private set
|
||||
var taggedPosts = setOf<Note>()
|
||||
private set
|
||||
|
||||
var reports = setOf<Note>()
|
||||
private set
|
||||
|
||||
var relays: Map<String, ContactListEvent.ReadWrite>? = null
|
||||
private set
|
||||
|
||||
val followers = Collections.synchronizedSet(mutableSetOf<User>())
|
||||
val messages = ConcurrentHashMap<User, MutableSet<Note>>()
|
||||
var relaysBeingUsed = mapOf<String, RelayInfo>()
|
||||
private set
|
||||
|
||||
val reports = Collections.synchronizedSet(mutableSetOf<Note>())
|
||||
val relaysBeingUsed = Collections.synchronizedMap(mutableMapOf<String, RelayInfo>())
|
||||
var messages = mapOf<User, Set<Note>>()
|
||||
private set
|
||||
|
||||
var latestMetadataRequestEOSE: Long? = null
|
||||
var latestReportRequestEOSE: Long? = null
|
||||
@ -64,8 +74,8 @@ class User(val pubkeyHex: String) {
|
||||
}
|
||||
|
||||
fun follow(user: User, followedAt: Long) {
|
||||
follows.add(user)
|
||||
user.followers.add(this)
|
||||
follows = follows + user
|
||||
user.followers = user.followers + this
|
||||
|
||||
liveFollows.invalidateData()
|
||||
user.liveFollows.invalidateData()
|
||||
@ -75,8 +85,8 @@ class User(val pubkeyHex: String) {
|
||||
}
|
||||
}
|
||||
fun unfollow(user: User) {
|
||||
follows.remove(user)
|
||||
user.followers.remove(this)
|
||||
follows = follows - user
|
||||
user.followers = user.followers - this
|
||||
|
||||
liveFollows.invalidateData()
|
||||
user.liveFollows.invalidateData()
|
||||
@ -87,42 +97,50 @@ class User(val pubkeyHex: String) {
|
||||
}
|
||||
|
||||
fun addTaggedPost(note: Note) {
|
||||
taggedPosts.add(note)
|
||||
updateSubscribers { it.onNewPosts() }
|
||||
if (note !in taggedPosts) {
|
||||
taggedPosts = taggedPosts + note
|
||||
updateSubscribers { it.onNewTaggedPosts() }
|
||||
}
|
||||
}
|
||||
|
||||
fun addNote(note: Note) {
|
||||
if (note !in notes) {
|
||||
notes = notes + note
|
||||
updateSubscribers { it.onNewNotes() }
|
||||
}
|
||||
}
|
||||
|
||||
fun addReport(note: Note) {
|
||||
if (reports.add(note)) {
|
||||
updateSubscribers { it.onNewReports() }
|
||||
if (note !in reports) {
|
||||
reports = reports + note
|
||||
liveReports.invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
fun reportsBy(user: User): List<Note> {
|
||||
return synchronized(reports) {
|
||||
reports.filter { it.author == user }
|
||||
}
|
||||
return reports.filter { it.author == user }
|
||||
}
|
||||
|
||||
fun reportsBy(users: Set<User>): List<Note> {
|
||||
return synchronized(reports) {
|
||||
reports.filter { it.author in users }
|
||||
}
|
||||
return reports.filter { it.author in users }
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getOrCreateChannel(user: User): MutableSet<Note> {
|
||||
fun getOrCreateChannel(user: User): Set<Note> {
|
||||
return messages[user] ?: run {
|
||||
val channel = Collections.synchronizedSet(mutableSetOf<Note>())
|
||||
messages[user] = channel
|
||||
val channel = setOf<Note>()
|
||||
messages = messages + Pair(user, channel)
|
||||
channel
|
||||
}
|
||||
}
|
||||
|
||||
fun addMessage(user: User, msg: Note) {
|
||||
getOrCreateChannel(user).add(msg)
|
||||
liveMessages.invalidateData()
|
||||
updateSubscribers { it.onNewMessage() }
|
||||
val channel = getOrCreateChannel(user)
|
||||
if (msg !in channel) {
|
||||
messages = messages + Pair(user, channel + msg)
|
||||
liveMessages.invalidateData()
|
||||
updateSubscribers { it.onNewMessage() }
|
||||
}
|
||||
}
|
||||
|
||||
data class RelayInfo (
|
||||
@ -132,9 +150,9 @@ class User(val pubkeyHex: String) {
|
||||
)
|
||||
|
||||
fun addRelay(relay: Relay, eventTime: Long) {
|
||||
val here = relaysBeingUsed.get(relay.url)
|
||||
val here = relaysBeingUsed[relay.url]
|
||||
if (here == null) {
|
||||
relaysBeingUsed.put(relay.url, RelayInfo(relay.url, eventTime, 1) )
|
||||
relaysBeingUsed = relaysBeingUsed + Pair(relay.url, RelayInfo(relay.url, eventTime, 1))
|
||||
} else {
|
||||
if (eventTime > here.lastEvent) {
|
||||
here.lastEvent = eventTime
|
||||
@ -147,12 +165,9 @@ class User(val pubkeyHex: String) {
|
||||
}
|
||||
|
||||
fun updateFollows(newFollows: Set<User>, updateAt: Long) {
|
||||
val toBeAdded = synchronized(follows) {
|
||||
newFollows - follows
|
||||
}
|
||||
val toBeRemoved = synchronized(follows) {
|
||||
follows - newFollows
|
||||
}
|
||||
val toBeAdded = newFollows - follows
|
||||
val toBeRemoved = follows - newFollows
|
||||
|
||||
toBeAdded.forEach {
|
||||
follow(it, updateAt)
|
||||
}
|
||||
@ -182,29 +197,21 @@ class User(val pubkeyHex: String) {
|
||||
}
|
||||
|
||||
fun isFollowing(user: User): Boolean {
|
||||
return synchronized(follows) {
|
||||
follows.contains(user)
|
||||
}
|
||||
}
|
||||
|
||||
fun getRelayKeysBeingUsed(): Set<String> {
|
||||
return synchronized(relaysBeingUsed) {
|
||||
relaysBeingUsed.keys.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
fun getRelayValuesBeingUsed(): List<RelayInfo> {
|
||||
return synchronized(relaysBeingUsed) {
|
||||
relaysBeingUsed.values.toList()
|
||||
}
|
||||
return follows.contains(user)
|
||||
}
|
||||
|
||||
fun hasSentMessagesTo(user: User?): Boolean {
|
||||
val messagesToUser = messages[user] ?: return false
|
||||
|
||||
return synchronized(messagesToUser) {
|
||||
messagesToUser.firstOrNull { this == it.author } != null
|
||||
}
|
||||
return messagesToUser.firstOrNull { this == it.author } != null
|
||||
}
|
||||
|
||||
fun hasReport(loggedIn: User, type: ReportEvent.ReportType): Boolean {
|
||||
return reports.firstOrNull {
|
||||
it.author == loggedIn
|
||||
&& it.event is ReportEvent
|
||||
&& (it.event as ReportEvent).reportType.contains(type)
|
||||
} != null
|
||||
}
|
||||
|
||||
// Model Observers
|
||||
@ -221,7 +228,8 @@ class User(val pubkeyHex: String) {
|
||||
abstract class Listener {
|
||||
open fun onRelayChange() = Unit
|
||||
open fun onFollowsChange() = Unit
|
||||
open fun onNewPosts() = Unit
|
||||
open fun onNewTaggedPosts() = Unit
|
||||
open fun onNewNotes() = Unit
|
||||
open fun onNewMessage() = Unit
|
||||
open fun onNewRelayInfo() = Unit
|
||||
open fun onNewReports() = Unit
|
||||
|
@ -6,20 +6,22 @@ object Constants {
|
||||
val defaultRelays = arrayOf(
|
||||
Relay("wss://nostr.bitcoiner.social", read = true, write = true),
|
||||
Relay("wss://relay.nostr.bg", read = true, write = true),
|
||||
//Relay("wss://brb.io", read = true, write = true),
|
||||
Relay("wss://brb.io", read = true, write = true),
|
||||
Relay("wss://relay.snort.social", read = true, write = true),
|
||||
Relay("wss://nostr.rocks", read = true, write = true),
|
||||
Relay("wss://relay.damus.io", read = true, write = true),
|
||||
Relay("wss://nostr.fmt.wiz.biz", read = true, write = true),
|
||||
Relay("wss://nostr.oxtr.dev", read = true, write = true),
|
||||
Relay("wss://eden.nostr.land", read = true, write = true),
|
||||
//Relay("wss://nostr-2.zebedee.cloud", read = true, write = true),
|
||||
Relay("wss://nostr.zebedee.cloud", read = true, write = true),
|
||||
Relay("wss://nostr-pub.wellorder.net", read = true, write = true),
|
||||
Relay("wss://nostr.mom", read = true, write = true),
|
||||
Relay("wss://nostr.orangepill.dev", read = true, write = true),
|
||||
Relay("wss://nostr-pub.semisol.dev", read = true, write = true),
|
||||
Relay("wss://nostr.onsats.org", read = true, write = true),
|
||||
Relay("wss://nostr.sandwich.farm", read = true, write = true),
|
||||
Relay("wss://relay.nostr.ch", read = true, write = true)
|
||||
Relay("wss://relay.nostr.ch", read = true, write = true),
|
||||
Relay("wss://no.str.cr", read = true, write = true),
|
||||
Relay("wss://nos.lol", read = true, write = true)
|
||||
)
|
||||
}
|
@ -43,7 +43,7 @@ class Nip19 {
|
||||
return null
|
||||
}
|
||||
|
||||
fun parseTLV(data: ByteArray): Map<Byte, MutableList<ByteArray>> {
|
||||
fun parseTLV(data: ByteArray): Map<Byte, List<ByteArray>> {
|
||||
var result = mutableMapOf<Byte, MutableList<ByteArray>>()
|
||||
var rest = data
|
||||
while (rest.isNotEmpty()) {
|
||||
|
@ -39,14 +39,8 @@ object NostrAccountDataSource: NostrDataSource<Note>("AccountData") {
|
||||
override fun feed(): List<Note> {
|
||||
val user = account.userProfile()
|
||||
|
||||
val follows = user.follows
|
||||
val followKeys = synchronized(follows) {
|
||||
follows.map { it.pubkeyHex }
|
||||
}
|
||||
val allowSet = followKeys.plus(user.pubkeyHex).toSet()
|
||||
|
||||
return LocalCache.notes.values
|
||||
.filter { (it.event is TextNoteEvent || it.event is RepostEvent) && it.author?.pubkeyHex in allowSet }
|
||||
.filter { (it.event is TextNoteEvent || it.event is RepostEvent) && it.author in user.follows }
|
||||
.sortedBy { it.event?.createdAt }
|
||||
.reversed()
|
||||
}
|
||||
|
@ -50,11 +50,7 @@ object NostrChatRoomDataSource: NostrDataSource<Note>("ChatroomFeed") {
|
||||
override fun feed(): List<Note> {
|
||||
val messages = account.userProfile().messages[withUser] ?: return emptyList()
|
||||
|
||||
val filteredMessages = synchronized(messages) {
|
||||
messages.filter { account.isAcceptable(it) }
|
||||
}
|
||||
|
||||
return filteredMessages.sortedBy { it.event?.createdAt }.reversed()
|
||||
return messages.filter { account.isAcceptable(it) }.sortedBy { it.event?.createdAt }.reversed()
|
||||
}
|
||||
|
||||
override fun updateChannelFilters() {
|
||||
|
@ -57,17 +57,10 @@ object NostrChatroomListDataSource: NostrDataSource<Note>("MailBoxFeed") {
|
||||
// returns the last Note of each user.
|
||||
override fun feed(): List<Note> {
|
||||
val messages = account.userProfile().messages
|
||||
val messagingWith = messages.keys().toList().filter { account.isAcceptable(it) }
|
||||
val messagingWith = messages.keys.filter { account.isAcceptable(it) }
|
||||
|
||||
val privateMessages = messagingWith.mapNotNull {
|
||||
val conversation = messages[it]
|
||||
if (conversation != null) {
|
||||
synchronized(conversation) {
|
||||
conversation.sortedBy { it.event?.createdAt }.lastOrNull { it.event != null }
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
messages[it]?.sortedBy { it.event?.createdAt }?.lastOrNull { it.event != null }
|
||||
}
|
||||
|
||||
val publicChannels = account.followingChannels().map {
|
||||
|
@ -35,10 +35,8 @@ import nostr.postr.events.RecommendRelayEvent
|
||||
import nostr.postr.events.TextNoteEvent
|
||||
|
||||
abstract class NostrDataSource<T>(val debugName: String) {
|
||||
private val channels = Collections.synchronizedSet(mutableSetOf<Channel>())
|
||||
private val channelIds = Collections.synchronizedSet(mutableSetOf<String>())
|
||||
|
||||
private val eventCounter = mutableMapOf<String, Int>()
|
||||
private var channels = mapOf<String, Channel>()
|
||||
private var eventCounter = mapOf<String, Int>()
|
||||
|
||||
fun printCounter() {
|
||||
eventCounter.forEach {
|
||||
@ -48,12 +46,13 @@ abstract class NostrDataSource<T>(val debugName: String) {
|
||||
|
||||
private val clientListener = object : Client.Listener() {
|
||||
override fun onEvent(event: Event, subscriptionId: String, relay: Relay) {
|
||||
if (subscriptionId in channelIds) {
|
||||
if (subscriptionId in channels.keys) {
|
||||
val key = "${debugName} ${subscriptionId} ${event.kind}"
|
||||
if (eventCounter.contains(key)) {
|
||||
eventCounter.put(key, eventCounter.get(key)!! + 1)
|
||||
val keyValue = eventCounter.get(key)
|
||||
if (keyValue != null) {
|
||||
eventCounter = eventCounter + Pair(key, keyValue + 1)
|
||||
} else {
|
||||
eventCounter.put(key, 1)
|
||||
eventCounter = eventCounter + Pair(key, 1)
|
||||
}
|
||||
|
||||
try {
|
||||
@ -102,7 +101,7 @@ abstract class NostrDataSource<T>(val debugName: String) {
|
||||
|
||||
if (type == Relay.Type.EOSE && channel != null) {
|
||||
// updates a per subscripton since date
|
||||
channels.filter { it.id == channel }.firstOrNull()?.updateEOSE(Date().time / 1000)
|
||||
channels[channel]?.updateEOSE(Date().time / 1000)
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,7 +119,7 @@ abstract class NostrDataSource<T>(val debugName: String) {
|
||||
}
|
||||
|
||||
open fun stop() {
|
||||
channels.forEach { channel ->
|
||||
channels.values.forEach { channel ->
|
||||
if (channel.filter != null) // if it is active, close
|
||||
Client.close(channel.id)
|
||||
}
|
||||
@ -148,15 +147,13 @@ abstract class NostrDataSource<T>(val debugName: String) {
|
||||
|
||||
fun requestNewChannel(onEOSE: ((Long) -> Unit)? = null): Channel {
|
||||
val newChannel = Channel(debugName+UUID.randomUUID().toString().substring(0,4), onEOSE)
|
||||
channels.add(newChannel)
|
||||
channelIds.add(newChannel.id)
|
||||
channels = channels + Pair(newChannel.id, newChannel)
|
||||
return newChannel
|
||||
}
|
||||
|
||||
fun dismissChannel(channel: Channel) {
|
||||
Client.close(channel.id)
|
||||
channels.remove(channel)
|
||||
channelIds.remove(channel.id)
|
||||
channels = channels.minus(channel.id)
|
||||
}
|
||||
|
||||
var handlerWaiting = false
|
||||
@ -182,14 +179,14 @@ abstract class NostrDataSource<T>(val debugName: String) {
|
||||
|
||||
fun resetFiltersSuspend() {
|
||||
// saves the channels that are currently active
|
||||
val activeChannels = channels.filter { it.filter != null }
|
||||
val activeChannels = channels.values.filter { it.filter != null }
|
||||
// saves the current content to only update if it changes
|
||||
val currentFilter = activeChannels.associate { it.id to it.filter!!.joinToString("|") { it.toJson() } }
|
||||
|
||||
updateChannelFilters()
|
||||
|
||||
// Makes sure to only send an updated filter when it actually changes.
|
||||
channels.forEach { channel ->
|
||||
channels.values.forEach { channel ->
|
||||
val channelsNewFilter = channel.filter
|
||||
|
||||
if (channel in activeChannels) {
|
||||
|
@ -31,12 +31,10 @@ object NostrHomeDataSource: NostrDataSource<Note>("HomeFeed") {
|
||||
}
|
||||
|
||||
fun createFollowAccountsFilter(): JsonFilter {
|
||||
val follows = account.userProfile().follows ?: emptySet()
|
||||
val follows = account.userProfile().follows
|
||||
|
||||
val followKeys = synchronized(follows) {
|
||||
follows.map {
|
||||
it.pubkey.toHex().substring(0, 6)
|
||||
}
|
||||
val followKeys = follows.map {
|
||||
it.pubkey.toHex().substring(0, 6)
|
||||
}
|
||||
|
||||
val followSet = followKeys.plus(account.userProfile().pubkeyHex.substring(0, 6))
|
||||
@ -69,15 +67,8 @@ object NostrHomeDataSource: NostrDataSource<Note>("HomeFeed") {
|
||||
override fun feed(): List<Note> {
|
||||
val user = account.userProfile()
|
||||
|
||||
val follows = user.follows
|
||||
val followKeys = synchronized(follows) {
|
||||
follows.map { it.pubkeyHex }
|
||||
}
|
||||
|
||||
val allowSet = followKeys.plus(user.pubkeyHex).toSet()
|
||||
|
||||
return LocalCache.notes.values
|
||||
.filter { (it.event is TextNoteEvent || it.event is RepostEvent) && it.author?.pubkeyHex in allowSet }
|
||||
.filter { (it.event is TextNoteEvent || it.event is RepostEvent) && it.author in user.follows }
|
||||
.filter { account.isAcceptable(it) }
|
||||
.sortedBy { it.event?.createdAt }
|
||||
.reversed()
|
||||
|
@ -13,15 +13,15 @@ object NostrNotificationDataSource: NostrDataSource<Note>("NotificationFeed") {
|
||||
lateinit var account: Account
|
||||
|
||||
override fun feed(): List<Note> {
|
||||
val set = account.userProfile().taggedPosts
|
||||
val filtered = synchronized(set) {
|
||||
set.filter { it.event != null }.filter { account.isAcceptable(it) }
|
||||
}
|
||||
|
||||
return filtered.filter {
|
||||
return account.userProfile().taggedPosts
|
||||
.filter { it.event != null }
|
||||
.filter { account.isAcceptable(it) }
|
||||
.filter {
|
||||
it.event !is ChannelCreateEvent
|
||||
&& it.event !is ChannelMetadataEvent
|
||||
}.sortedBy { it.event?.createdAt }.reversed()
|
||||
}
|
||||
.sortedBy { it.event?.createdAt }
|
||||
.reversed()
|
||||
}
|
||||
|
||||
override fun updateChannelFilters() {}
|
||||
|
@ -18,7 +18,7 @@ object NostrSingleEventDataSource: NostrDataSource<Note>("SingleEventFeed") {
|
||||
private var eventsToWatch = setOf<String>()
|
||||
|
||||
private fun createRepliesAndReactionsFilter(): List<JsonFilter>? {
|
||||
val reactionsToWatch = eventsToWatch.map { it }
|
||||
val reactionsToWatch = eventsToWatch.map { LocalCache.getOrCreateNote(it) }
|
||||
|
||||
if (reactionsToWatch.isEmpty()) {
|
||||
return null
|
||||
@ -26,16 +26,16 @@ object NostrSingleEventDataSource: NostrDataSource<Note>("SingleEventFeed") {
|
||||
|
||||
val now = Date().time / 1000
|
||||
|
||||
return eventsToWatch.filter {
|
||||
val lastTime = LocalCache.getOrCreateNote(it).lastReactionsDownloadTime;
|
||||
return reactionsToWatch.filter {
|
||||
val lastTime = it.lastReactionsDownloadTime;
|
||||
lastTime == null || lastTime < (now - 10)
|
||||
}.map {
|
||||
JsonFilter(
|
||||
kinds = listOf(
|
||||
TextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind, ReportEvent.kind
|
||||
),
|
||||
tags = mapOf("e" to listOf(it)),
|
||||
since = LocalCache.getOrCreateNote(it).lastReactionsDownloadTime
|
||||
tags = mapOf("e" to listOf(it.idHex)),
|
||||
since = it.lastReactionsDownloadTime
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -9,28 +9,23 @@ import nostr.postr.JsonFilter
|
||||
import nostr.postr.events.TextNoteEvent
|
||||
|
||||
object NostrThreadDataSource: NostrDataSource<Note>("SingleThreadFeed") {
|
||||
val eventsToWatch = Collections.synchronizedList(mutableListOf<String>())
|
||||
private var eventsToWatch = setOf<String>()
|
||||
|
||||
fun createRepliesAndReactionsFilter(): JsonFilter? {
|
||||
val reactionsToWatch = eventsToWatch.map { it }
|
||||
|
||||
if (reactionsToWatch.isEmpty()) {
|
||||
if (eventsToWatch.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return JsonFilter(
|
||||
kinds = listOf(TextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind),
|
||||
tags = mapOf("e" to reactionsToWatch)
|
||||
tags = mapOf("e" to eventsToWatch.toList())
|
||||
)
|
||||
}
|
||||
|
||||
fun createLoadEventsIfNotLoadedFilter(): JsonFilter? {
|
||||
val nodes = synchronized(eventsToWatch) {
|
||||
eventsToWatch.map { LocalCache.notes[it] }
|
||||
}
|
||||
val nodes = eventsToWatch.map { LocalCache.getOrCreateNote(it) }
|
||||
|
||||
val eventsToLoad = nodes
|
||||
.filterNotNull()
|
||||
.filter { it.event == null }
|
||||
.map { it.idHex.substring(0, 8) }
|
||||
|
||||
@ -46,11 +41,12 @@ object NostrThreadDataSource: NostrDataSource<Note>("SingleThreadFeed") {
|
||||
val loadEventsChannel = requestNewChannel()
|
||||
|
||||
override fun feed(): List<Note> {
|
||||
return synchronized(eventsToWatch) {
|
||||
eventsToWatch.map {
|
||||
LocalCache.notes[it]
|
||||
}.filterNotNull()
|
||||
}
|
||||
// Currently orders by date of each event, descending, at each level of the reply stack
|
||||
val order = compareByDescending<Note> { it.replyLevelSignature() }
|
||||
|
||||
return eventsToWatch.map {
|
||||
LocalCache.getOrCreateNote(it)
|
||||
}.sortedWith(order)
|
||||
}
|
||||
|
||||
override fun updateChannelFilters() {
|
||||
@ -84,9 +80,9 @@ object NostrThreadDataSource: NostrDataSource<Note>("SingleThreadFeed") {
|
||||
}
|
||||
|
||||
fun loadThread(noteId: String) {
|
||||
val note = LocalCache.notes[noteId]
|
||||
val note = LocalCache.getOrCreateNote(noteId)
|
||||
|
||||
if (note != null) {
|
||||
if (note.event != null) {
|
||||
val thread = mutableListOf<Note>()
|
||||
val threadSet = mutableSetOf<Note>()
|
||||
|
||||
@ -94,17 +90,12 @@ object NostrThreadDataSource: NostrDataSource<Note>("SingleThreadFeed") {
|
||||
|
||||
loadDown(threadRoot, thread, threadSet)
|
||||
|
||||
// Currently orders by date of each event, descending, at each level of the reply stack
|
||||
val order = compareByDescending<Note> { it.replyLevelSignature() }
|
||||
|
||||
eventsToWatch.clear()
|
||||
eventsToWatch.addAll(thread.sortedWith(order).map { it.idHex })
|
||||
eventsToWatch = thread.map { it.idHex }.toSet()
|
||||
} else {
|
||||
eventsToWatch.clear()
|
||||
eventsToWatch.add(noteId)
|
||||
eventsToWatch = setOf(noteId)
|
||||
}
|
||||
|
||||
resetFilters()
|
||||
invalidateFilters()
|
||||
}
|
||||
|
||||
fun loadDown(note: Note, thread: MutableList<Note>, threadSet: MutableSet<Note>) {
|
||||
@ -112,9 +103,7 @@ object NostrThreadDataSource: NostrDataSource<Note>("SingleThreadFeed") {
|
||||
thread.add(note)
|
||||
threadSet.add(note)
|
||||
|
||||
synchronized(note.replies) {
|
||||
note.replies.toList()
|
||||
}.forEach {
|
||||
note.replies.forEach {
|
||||
loadDown(it, thread, threadSet)
|
||||
}
|
||||
}
|
||||
|
@ -51,11 +51,11 @@ object NostrUserProfileDataSource: NostrDataSource<Note>("UserProfileFeed") {
|
||||
val userInfoChannel = requestNewChannel()
|
||||
|
||||
override fun feed(): List<Note> {
|
||||
val notes = user?.notes ?: return emptyList()
|
||||
val sortedNotes = synchronized(notes) {
|
||||
notes.filter { account.isAcceptable(it) }.sortedBy { it.event?.createdAt }
|
||||
}
|
||||
return sortedNotes.reversed()
|
||||
return user?.notes
|
||||
?.filter { account.isAcceptable(it) }
|
||||
?.sortedBy { it.event?.createdAt }
|
||||
?.reversed()
|
||||
?: emptyList()
|
||||
}
|
||||
|
||||
override fun updateChannelFilters() {
|
||||
|
@ -16,11 +16,7 @@ object NostrUserProfileFollowersDataSource: NostrDataSource<User>("UserProfileFo
|
||||
}
|
||||
|
||||
override fun feed(): List<User> {
|
||||
val followers = user?.followers ?: emptyList()
|
||||
|
||||
return synchronized(followers) {
|
||||
followers.filter { account.isAcceptable(it) }.toList()
|
||||
}
|
||||
return user?.followers?.filter { account.isAcceptable(it) } ?: emptyList()
|
||||
}
|
||||
|
||||
override fun updateChannelFilters() {}
|
||||
|
@ -16,11 +16,7 @@ object NostrUserProfileFollowsDataSource: NostrDataSource<User>("UserProfileFoll
|
||||
}
|
||||
|
||||
override fun feed(): List<User> {
|
||||
val follows = user?.follows ?: emptyList()
|
||||
|
||||
return synchronized(follows) {
|
||||
follows.filter { account.isAcceptable(it) }.toList()
|
||||
}
|
||||
return user?.follows?.filter { account.isAcceptable(it) } ?: emptyList()
|
||||
}
|
||||
|
||||
override fun updateChannelFilters() {}
|
||||
|
@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.service.relays
|
||||
import android.util.Log
|
||||
import com.google.gson.JsonElement
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import nostr.postr.events.Event
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
@ -15,13 +16,18 @@ class Relay(
|
||||
var read: Boolean = true,
|
||||
var write: Boolean = true
|
||||
) {
|
||||
private val httpClient = OkHttpClient()
|
||||
private val httpClient = OkHttpClient.Builder()
|
||||
.connectTimeout(10, TimeUnit.SECONDS)
|
||||
.readTimeout(10, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
private var listeners = setOf<Listener>()
|
||||
private var socket: WebSocket? = null
|
||||
|
||||
var eventDownloadCounter = 0
|
||||
var eventUploadCounter = 0
|
||||
var errorCounter = 0
|
||||
var ping: Long? = null
|
||||
|
||||
var closingTime = 0L
|
||||
|
||||
@ -43,6 +49,8 @@ class Relay(
|
||||
val listener = object : WebSocketListener() {
|
||||
|
||||
override fun onOpen(webSocket: WebSocket, response: Response) {
|
||||
ping = response.receivedResponseAtMillis - response.sentRequestAtMillis
|
||||
|
||||
// Sends everything.
|
||||
Client.allSubscriptions().forEach {
|
||||
sendFilter(requestId = it)
|
||||
@ -109,8 +117,8 @@ class Relay(
|
||||
socket = null
|
||||
closingTime = Date().time / 1000
|
||||
|
||||
Log.w("Relay", "Relay onFailure ${url}, ${response?.message}")
|
||||
//t.printStackTrace()
|
||||
Log.w("Relay", "Relay onFailure $url, ${response?.message}")
|
||||
t.printStackTrace()
|
||||
listeners.forEach {
|
||||
it.onError(this@Relay, "", Error("WebSocket Failure. Response: ${response}. Exception: ${t.message}", t))
|
||||
}
|
||||
@ -120,7 +128,7 @@ class Relay(
|
||||
socket = httpClient.newWebSocket(request, listener)
|
||||
} catch (e: Exception) {
|
||||
closingTime = Date().time / 1000
|
||||
Log.e("Relay", "Relay Invalid ${url}")
|
||||
Log.e("Relay", "Relay Invalid $url")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class NewPostViewModel: ViewModel() {
|
||||
private var originalNote: Note? = null
|
||||
|
||||
var mentions by mutableStateOf<List<User>?>(null)
|
||||
var replyTos by mutableStateOf<MutableList<Note>?>(null)
|
||||
var replyTos by mutableStateOf<List<Note>?>(null)
|
||||
|
||||
var message by mutableStateOf(TextFieldValue(""))
|
||||
var urlPreview by mutableStateOf<String?>(null)
|
||||
@ -37,7 +37,7 @@ class NewPostViewModel: ViewModel() {
|
||||
fun load(account: Account, replyingTo: Note?) {
|
||||
originalNote = replyingTo
|
||||
replyingTo?.let { replyNote ->
|
||||
this.replyTos = (replyNote.replyTo ?: mutableListOf()).plus(replyNote).toMutableList()
|
||||
this.replyTos = (replyNote.replyTo ?: emptyList()).plus(replyNote)
|
||||
replyNote.author?.let { replyUser ->
|
||||
val currentMentions = replyNote.mentions ?: emptyList()
|
||||
if (currentMentions.contains(replyUser)) {
|
||||
|
@ -115,7 +115,7 @@ private fun homeHasNewItems(cache: NotificationCache): Boolean {
|
||||
val homeFeed = NostrHomeDataSource.feed().take(100)
|
||||
|
||||
val hasNewInFollows = homeFeed.filter {
|
||||
it.event is RepostEvent || it.replyTo == null || it.replyTo?.size == 0
|
||||
it.isNewThread()
|
||||
}.filter {
|
||||
(it.event?.createdAt ?: 0) > lastTimeFollows
|
||||
}.isNotEmpty()
|
||||
|
@ -24,14 +24,14 @@ import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
|
||||
@Composable
|
||||
fun ReplyInformation(replyTo: MutableList<Note>?, mentions: List<User>?, navController: NavController) {
|
||||
fun ReplyInformation(replyTo: List<Note>?, mentions: List<User>?, navController: NavController) {
|
||||
ReplyInformation(replyTo, mentions) {
|
||||
navController.navigate("User/${it.pubkeyHex}")
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ReplyInformation(replyTo: MutableList<Note>?, mentions: List<User>?, prefix: String = "", onUserTagClick: (User) -> Unit) {
|
||||
fun ReplyInformation(replyTo: List<Note>?, mentions: List<User>?, prefix: String = "", onUserTagClick: (User) -> Unit) {
|
||||
FlowRow() {
|
||||
if (mentions != null && mentions.isNotEmpty()) {
|
||||
if (replyTo != null && replyTo.isNotEmpty()) {
|
||||
@ -76,7 +76,7 @@ fun ReplyInformation(replyTo: MutableList<Note>?, mentions: List<User>?, prefix:
|
||||
|
||||
|
||||
@Composable
|
||||
fun ReplyInformationChannel(replyTo: MutableList<Note>?, mentions: List<User>?, channel: Channel, navController: NavController) {
|
||||
fun ReplyInformationChannel(replyTo: List<Note>?, mentions: List<User>?, channel: Channel, navController: NavController) {
|
||||
ReplyInformationChannel(replyTo, mentions, channel,
|
||||
onUserTagClick = {
|
||||
navController.navigate("User/${it.pubkeyHex}")
|
||||
@ -89,7 +89,7 @@ fun ReplyInformationChannel(replyTo: MutableList<Note>?, mentions: List<User>?,
|
||||
|
||||
|
||||
@Composable
|
||||
fun ReplyInformationChannel(replyTo: MutableList<Note>?,
|
||||
fun ReplyInformationChannel(replyTo: List<Note>?,
|
||||
mentions: List<User>?,
|
||||
baseChannel: Channel,
|
||||
prefix: String = "",
|
||||
|
@ -54,21 +54,17 @@ class NostrChatroomListNewFeedViewModel: FeedViewModel(NostrChatroomListDataSour
|
||||
}
|
||||
}
|
||||
|
||||
fun isNewThread(note: Note): Boolean {
|
||||
return note.event is RepostEvent || note.replyTo == null || note.replyTo?.size == 0
|
||||
}
|
||||
|
||||
class NostrHomeFeedViewModel: FeedViewModel(NostrHomeDataSource) {
|
||||
override fun newListFromDataSource(): List<Note> {
|
||||
// Filter: no replies
|
||||
return dataSource.feed().filter { isNewThread(it) }.take(100)
|
||||
return dataSource.feed().filter { it.isNewThread() }.take(100)
|
||||
}
|
||||
}
|
||||
|
||||
class NostrHomeRepliesFeedViewModel: FeedViewModel(NostrHomeDataSource) {
|
||||
override fun newListFromDataSource(): List<Note> {
|
||||
// Filter: only replies
|
||||
return dataSource.feed().filter {!isNewThread(it) }.take(100)
|
||||
return dataSource.feed().filter {! it.isNewThread() }.take(100)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,8 +42,8 @@ class RelayFeedViewModel: ViewModel() {
|
||||
|
||||
fun refresh() {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
val beingUsed = currentUser?.getRelayValuesBeingUsed() ?: emptyList()
|
||||
val beingUsedSet = currentUser?.getRelayKeysBeingUsed() ?: emptySet()
|
||||
val beingUsed = currentUser?.relaysBeingUsed?.values ?: emptyList()
|
||||
val beingUsedSet = currentUser?.relaysBeingUsed?.keys ?: emptySet()
|
||||
|
||||
val newRelaysFromRecord = currentUser?.relays?.entries?.mapNotNull {
|
||||
if (it.key !in beingUsedSet) {
|
||||
|
Loading…
Reference in New Issue
Block a user