mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-10-10 20:53:42 +02:00
Improving performance with more precise filters.
This commit is contained in:
@@ -65,11 +65,11 @@ object LocalCache {
|
|||||||
|
|
||||||
|
|
||||||
fun consume(event: MetadataEvent) {
|
fun consume(event: MetadataEvent) {
|
||||||
//Log.d("MT", "New User ${users.size} ${event.contactMetaData.name}")
|
|
||||||
|
|
||||||
// new event
|
// new event
|
||||||
val oldUser = getOrCreateUser(event.pubKey)
|
val oldUser = getOrCreateUser(event.pubKey)
|
||||||
if (event.createdAt > oldUser.updatedMetadataAt) {
|
if (event.createdAt > oldUser.updatedMetadataAt) {
|
||||||
|
//Log.d("MT", "New User ${users.size} ${event.contactMetaData.name}")
|
||||||
|
|
||||||
val newUser = try {
|
val newUser = try {
|
||||||
metadataParser.readValue<UserMetadata>(ByteArrayInputStream(event.content.toByteArray(Charsets.UTF_8)), UserMetadata::class.java)
|
metadataParser.readValue<UserMetadata>(ByteArrayInputStream(event.content.toByteArray(Charsets.UTF_8)), UserMetadata::class.java)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -101,7 +101,7 @@ object LocalCache {
|
|||||||
|
|
||||||
note.loadEvent(event, author, mentions, replyTo)
|
note.loadEvent(event, author, mentions, replyTo)
|
||||||
|
|
||||||
//Log.d("TN", "New Note (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${note.event?.content} ${formattedDateTime(event.createdAt)}")
|
//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.
|
// Prepares user's profile view.
|
||||||
author.notes.add(note)
|
author.notes.add(note)
|
||||||
@@ -188,7 +188,7 @@ object LocalCache {
|
|||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
//Log.d("TN", "New Boost (${notes.size},${users.size}) ${note.author.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
//Log.d("TN", "New Boost (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
||||||
|
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
val mentions = event.originalAuthor.map { getOrCreateUser(decodePublicKey(it)) }.toList()
|
val mentions = event.originalAuthor.map { getOrCreateUser(decodePublicKey(it)) }.toList()
|
||||||
|
@@ -102,14 +102,13 @@ class Note(val idHex: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class NoteLiveData(val note: Note): LiveData<NoteState>(NoteState(note)) {
|
class NoteLiveData(val note: Note): LiveData<NoteState>(NoteState(note)) {
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
|
||||||
|
|
||||||
fun refresh() {
|
fun refresh() {
|
||||||
postValue(NoteState(note))
|
postValue(NoteState(note))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActive() {
|
override fun onActive() {
|
||||||
super.onActive()
|
super.onActive()
|
||||||
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
NostrSingleEventDataSource.add(note.idHex)
|
NostrSingleEventDataSource.add(note.idHex)
|
||||||
}
|
}
|
||||||
@@ -117,6 +116,7 @@ class NoteLiveData(val note: Note): LiveData<NoteState>(NoteState(note)) {
|
|||||||
|
|
||||||
override fun onInactive() {
|
override fun onInactive() {
|
||||||
super.onInactive()
|
super.onInactive()
|
||||||
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
NostrSingleEventDataSource.remove(note.idHex)
|
NostrSingleEventDataSource.remove(note.idHex)
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,15 @@
|
|||||||
package com.vitorpamplona.amethyst.model
|
package com.vitorpamplona.amethyst.model
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
||||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import nostr.postr.events.ContactListEvent
|
import nostr.postr.events.ContactListEvent
|
||||||
|
|
||||||
class User(val pubkey: ByteArray) {
|
class User(val pubkey: ByteArray) {
|
||||||
@@ -138,13 +143,19 @@ class UserLiveData(val user: User): LiveData<UserState>(UserState(user)) {
|
|||||||
|
|
||||||
override fun onActive() {
|
override fun onActive() {
|
||||||
super.onActive()
|
super.onActive()
|
||||||
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
scope.launch {
|
||||||
NostrSingleUserDataSource.add(user.pubkeyHex)
|
NostrSingleUserDataSource.add(user.pubkeyHex)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onInactive() {
|
override fun onInactive() {
|
||||||
super.onInactive()
|
super.onInactive()
|
||||||
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
scope.launch {
|
||||||
NostrSingleUserDataSource.remove(user.pubkeyHex)
|
NostrSingleUserDataSource.remove(user.pubkeyHex)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserState(val user: User)
|
class UserState(val user: User)
|
||||||
|
@@ -6,5 +6,5 @@ import nostr.postr.JsonFilter
|
|||||||
data class Channel (
|
data class Channel (
|
||||||
val id: String = UUID.randomUUID().toString().substring(0,4)
|
val id: String = UUID.randomUUID().toString().substring(0,4)
|
||||||
) {
|
) {
|
||||||
var filter: JsonFilter? = null // Inactive when null
|
var filter: List<JsonFilter>? = null // Inactive when null
|
||||||
}
|
}
|
@@ -29,31 +29,24 @@ object NostrAccountDataSource: NostrDataSource<Note>("AccountData") {
|
|||||||
account.userProfile().live.removeObserver(cacheListener)
|
account.userProfile().live.removeObserver(cacheListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createAccountFilter(): JsonFilter {
|
fun createAccountContactListFilter(): JsonFilter {
|
||||||
return JsonFilter(
|
return JsonFilter(
|
||||||
kinds = listOf(MetadataEvent.kind, ContactListEvent.kind),
|
kinds = listOf(ContactListEvent.kind),
|
||||||
authors = listOf(account.userProfile().pubkeyHex),
|
authors = listOf(account.userProfile().pubkeyHex),
|
||||||
since = System.currentTimeMillis() / 1000 - (60 * 60 * 24 * 7), // 4 days
|
limit = 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val accountChannel = requestNewChannel()
|
fun createAccountMetadataFilter(): JsonFilter {
|
||||||
|
return JsonFilter(
|
||||||
fun <T> equalsIgnoreOrder(list1:List<T>?, list2:List<T>?): Boolean {
|
kinds = listOf(MetadataEvent.kind),
|
||||||
if (list1 == null && list2 == null) return true
|
authors = listOf(account.userProfile().pubkeyHex),
|
||||||
if (list1 == null) return false
|
limit = 1
|
||||||
if (list2 == null) return false
|
)
|
||||||
|
|
||||||
return list1.size == list2.size && list1.toSet() == list2.toSet()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun equalAuthors(list1:JsonFilter?, list2:JsonFilter?): Boolean {
|
val accountMetadataChannel = requestNewChannel()
|
||||||
if (list1 == null && list2 == null) return true
|
val accountContactListChannel = requestNewChannel()
|
||||||
if (list1 == null) return false
|
|
||||||
if (list2 == null) return false
|
|
||||||
|
|
||||||
return equalsIgnoreOrder(list1.authors, list2.authors)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun feed(): List<Note> {
|
override fun feed(): List<Note> {
|
||||||
val user = account.userProfile()
|
val user = account.userProfile()
|
||||||
@@ -72,10 +65,10 @@ object NostrAccountDataSource: NostrDataSource<Note>("AccountData") {
|
|||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
// gets everthing about the user logged in
|
// gets everthing about the user logged in
|
||||||
val newAccountFilter = createAccountFilter()
|
val newAccountMetadataFilter = createAccountMetadataFilter()
|
||||||
|
accountMetadataChannel.filter = listOf(newAccountMetadataFilter).ifEmpty { null }
|
||||||
|
|
||||||
if (!equalAuthors(newAccountFilter, accountChannel.filter)) {
|
val newAccountContactListEvent = createAccountContactListFilter()
|
||||||
accountChannel.filter = newAccountFilter
|
accountContactListChannel.filter = listOf(newAccountContactListEvent).ifEmpty { null }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -15,7 +15,7 @@ object NostrChannelDataSource: NostrDataSource<Note>("ChatroomFeed") {
|
|||||||
fun createMessagesToChannelFilter() = JsonFilter(
|
fun createMessagesToChannelFilter() = JsonFilter(
|
||||||
kinds = listOf(ChannelMessageEvent.kind),
|
kinds = listOf(ChannelMessageEvent.kind),
|
||||||
tags = mapOf("e" to listOf(channel?.idHex).filterNotNull()),
|
tags = mapOf("e" to listOf(channel?.idHex).filterNotNull()),
|
||||||
since = System.currentTimeMillis() / 1000 - (60 * 60 * 24 * 1), // 24 hours
|
limit = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
val messagesChannel = requestNewChannel()
|
val messagesChannel = requestNewChannel()
|
||||||
@@ -26,6 +26,6 @@ object NostrChannelDataSource: NostrDataSource<Note>("ChatroomFeed") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
messagesChannel.filter = createMessagesToChannelFilter()
|
messagesChannel.filter = listOf(createMessagesToChannelFilter()).ifEmpty { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -39,7 +39,7 @@ object NostrChatRoomDataSource: NostrDataSource<Note>("ChatroomFeed") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
incomingChannel.filter = createMessagesToMeFilter()
|
incomingChannel.filter = listOf(createMessagesToMeFilter()).ifEmpty { null }
|
||||||
outgoingChannel.filter = createMessagesFromMeFilter()
|
outgoingChannel.filter = listOf(createMessagesFromMeFilter()).ifEmpty { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -26,16 +26,25 @@ object NostrChatroomListDataSource: NostrDataSource<Note>("MailBoxFeed") {
|
|||||||
ids = account.followingChannels.toList()
|
ids = account.followingChannels.toList()
|
||||||
)
|
)
|
||||||
|
|
||||||
fun createMyChannelsInfoFilter() = JsonFilter(
|
fun createLastChannelInfoFilter(): List<JsonFilter> {
|
||||||
|
return account.followingChannels.map {
|
||||||
|
JsonFilter(
|
||||||
kinds = listOf(ChannelMetadataEvent.kind),
|
kinds = listOf(ChannelMetadataEvent.kind),
|
||||||
tags = mapOf("e" to account.followingChannels.toList())
|
tags = mapOf("e" to listOf(it)),
|
||||||
|
limit = 1
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun createMessagesToMyChannelsFilter() = JsonFilter(
|
fun createLastMessageOfEachChannelFilter(): List<JsonFilter> {
|
||||||
|
return account.followingChannels.map {
|
||||||
|
JsonFilter(
|
||||||
kinds = listOf(ChannelMessageEvent.kind),
|
kinds = listOf(ChannelMessageEvent.kind),
|
||||||
tags = mapOf("e" to account.followingChannels.toList()),
|
tags = mapOf("e" to listOf(it)),
|
||||||
since = System.currentTimeMillis() / 1000 - (60 * 60 * 24 * 1), // 24 hours
|
limit = 1
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val incomingChannel = requestNewChannel()
|
val incomingChannel = requestNewChannel()
|
||||||
val outgoingChannel = requestNewChannel()
|
val outgoingChannel = requestNewChannel()
|
||||||
@@ -61,10 +70,10 @@ object NostrChatroomListDataSource: NostrDataSource<Note>("MailBoxFeed") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
incomingChannel.filter = createMessagesToMeFilter()
|
incomingChannel.filter = listOf(createMessagesToMeFilter()).ifEmpty { null }
|
||||||
outgoingChannel.filter = createMessagesFromMeFilter()
|
outgoingChannel.filter = listOf(createMessagesFromMeFilter()).ifEmpty { null }
|
||||||
myChannelsChannel.filter = createMyChannelsFilter()
|
myChannelsChannel.filter = listOf(createMyChannelsFilter()).ifEmpty { null }
|
||||||
myChannelsInfoChannel.filter = createMyChannelsInfoFilter()
|
myChannelsInfoChannel.filter = createLastChannelInfoFilter().ifEmpty { null }
|
||||||
myChannelsMessagesChannel.filter = createMessagesToMyChannelsFilter()
|
myChannelsMessagesChannel.filter = createLastMessageOfEachChannelFilter().ifEmpty { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -23,9 +23,25 @@ abstract class NostrDataSource<T>(val debugName: String) {
|
|||||||
private val channels = Collections.synchronizedSet(mutableSetOf<Channel>())
|
private val channels = Collections.synchronizedSet(mutableSetOf<Channel>())
|
||||||
private val channelIds = Collections.synchronizedSet(mutableSetOf<String>())
|
private val channelIds = Collections.synchronizedSet(mutableSetOf<String>())
|
||||||
|
|
||||||
|
private val eventCounter = mutableMapOf<String, Int>()
|
||||||
|
|
||||||
|
fun printCounter() {
|
||||||
|
eventCounter.forEach {
|
||||||
|
println("AAA Count ${it.key}: ${it.value}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 channelIds) {
|
if (subscriptionId in channelIds) {
|
||||||
|
val key = "${debugName} ${subscriptionId} ${event.kind}"
|
||||||
|
if (eventCounter.contains(key)) {
|
||||||
|
eventCounter.put(key, eventCounter.get(key)!! + 1)
|
||||||
|
} else {
|
||||||
|
eventCounter.put(key, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
//println("AAA ${debugName} ${subscriptionId} ${event.kind}")
|
||||||
when (event) {
|
when (event) {
|
||||||
is MetadataEvent -> LocalCache.consume(event)
|
is MetadataEvent -> LocalCache.consume(event)
|
||||||
is TextNoteEvent -> LocalCache.consume(event)
|
is TextNoteEvent -> LocalCache.consume(event)
|
||||||
@@ -109,7 +125,7 @@ abstract class NostrDataSource<T>(val debugName: String) {
|
|||||||
// saves the channels that are currently active
|
// saves the channels that are currently active
|
||||||
val activeChannels = channels.filter { it.filter != null }
|
val activeChannels = channels.filter { it.filter != null }
|
||||||
// saves the current content to only update if it changes
|
// saves the current content to only update if it changes
|
||||||
val currentFilter = activeChannels.associate { it.id to it.filter!!.toJson() }
|
val currentFilter = activeChannels.associate { it.id to it.filter!!.joinToString("|") { it.toJson() } }
|
||||||
|
|
||||||
updateChannelFilters()
|
updateChannelFilters()
|
||||||
|
|
||||||
@@ -123,12 +139,12 @@ abstract class NostrDataSource<T>(val debugName: String) {
|
|||||||
Client.close(channel.id)
|
Client.close(channel.id)
|
||||||
} else {
|
} else {
|
||||||
// was active and is still active, check if it has changed.
|
// was active and is still active, check if it has changed.
|
||||||
if (channelsNewFilter.toJson() != currentFilter[channel.id]) {
|
if (channelsNewFilter.joinToString("|") { it.toJson() } != currentFilter[channel.id]) {
|
||||||
Client.close(channel.id)
|
Client.close(channel.id)
|
||||||
Client.sendFilter(channel.id, mutableListOf(channelsNewFilter))
|
Client.sendFilter(channel.id, channelsNewFilter)
|
||||||
} else {
|
} else {
|
||||||
// hasn't changed, does nothing.
|
// hasn't changed, does nothing.
|
||||||
Client.sendFilterOnlyIfDisconnected(channel.id, mutableListOf(channelsNewFilter))
|
Client.sendFilterOnlyIfDisconnected(channel.id, channelsNewFilter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -136,8 +152,8 @@ abstract class NostrDataSource<T>(val debugName: String) {
|
|||||||
// was not active and is still not active, does nothing
|
// was not active and is still not active, does nothing
|
||||||
} else {
|
} else {
|
||||||
// was not active and becomes active, sends the filter.
|
// was not active and becomes active, sends the filter.
|
||||||
if (channelsNewFilter.toJson() != currentFilter[channel.id]) {
|
if (channelsNewFilter.joinToString("|") { it.toJson() } != currentFilter[channel.id]) {
|
||||||
Client.sendFilter(channel.id, mutableListOf(channelsNewFilter))
|
Client.sendFilter(channel.id, channelsNewFilter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,31 +6,13 @@ import nostr.postr.JsonFilter
|
|||||||
import nostr.postr.events.TextNoteEvent
|
import nostr.postr.events.TextNoteEvent
|
||||||
|
|
||||||
object NostrGlobalDataSource: NostrDataSource<Note>("GlobalFeed") {
|
object NostrGlobalDataSource: NostrDataSource<Note>("GlobalFeed") {
|
||||||
val fifteenMinutes = (60*15) // 15 mins
|
|
||||||
|
|
||||||
fun createGlobalFilter() = JsonFilter(
|
fun createGlobalFilter() = JsonFilter(
|
||||||
kinds = listOf(TextNoteEvent.kind),
|
kinds = listOf(TextNoteEvent.kind),
|
||||||
since = System.currentTimeMillis() / 1000 - fifteenMinutes
|
limit = 200
|
||||||
)
|
)
|
||||||
|
|
||||||
val globalFeedChannel = requestNewChannel()
|
val globalFeedChannel = requestNewChannel()
|
||||||
|
|
||||||
fun equalTime(list1:Long?, list2:Long?): Boolean {
|
|
||||||
if (list1 == null && list2 == null) return true
|
|
||||||
if (list1 == null) return false
|
|
||||||
if (list2 == null) return false
|
|
||||||
|
|
||||||
return Math.abs(list1 - list2) < (4*fifteenMinutes)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun equalFilters(list1:JsonFilter?, list2:JsonFilter?): Boolean {
|
|
||||||
if (list1 == null && list2 == null) return true
|
|
||||||
if (list1 == null) return false
|
|
||||||
if (list2 == null) return false
|
|
||||||
|
|
||||||
return equalTime(list1.since, list2.since)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun feed() = LocalCache.notes.values
|
override fun feed() = LocalCache.notes.values
|
||||||
.filter {
|
.filter {
|
||||||
it.event is TextNoteEvent && (it.event as TextNoteEvent).replyTos.isEmpty()
|
it.event is TextNoteEvent && (it.event as TextNoteEvent).replyTos.isEmpty()
|
||||||
@@ -39,10 +21,6 @@ object NostrGlobalDataSource: NostrDataSource<Note>("GlobalFeed") {
|
|||||||
.reversed()
|
.reversed()
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
val newFilter = createGlobalFilter()
|
globalFeedChannel.filter = listOf(createGlobalFilter()).ifEmpty { null }
|
||||||
|
|
||||||
if (!equalFilters(newFilter, globalFeedChannel.filter)) {
|
|
||||||
globalFeedChannel.filter = newFilter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -28,7 +28,7 @@ object NostrHomeDataSource: NostrDataSource<Note>("HomeFeed") {
|
|||||||
account.userProfile().live.removeObserver(cacheListener)
|
account.userProfile().live.removeObserver(cacheListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createFollowAccountsFilter(): JsonFilter? {
|
fun createFollowAccountsFilter(): JsonFilter {
|
||||||
val follows = account.userProfile().follows ?: emptySet()
|
val follows = account.userProfile().follows ?: emptySet()
|
||||||
|
|
||||||
val followKeys = synchronized(follows) {
|
val followKeys = synchronized(follows) {
|
||||||
@@ -39,12 +39,10 @@ object NostrHomeDataSource: NostrDataSource<Note>("HomeFeed") {
|
|||||||
|
|
||||||
val followSet = followKeys.plus(account.userProfile().pubkeyHex.substring(0, 6))
|
val followSet = followKeys.plus(account.userProfile().pubkeyHex.substring(0, 6))
|
||||||
|
|
||||||
if (followSet.isEmpty()) return null
|
|
||||||
|
|
||||||
return JsonFilter(
|
return JsonFilter(
|
||||||
kinds = listOf(TextNoteEvent.kind, RepostEvent.kind),
|
kinds = listOf(TextNoteEvent.kind, RepostEvent.kind),
|
||||||
authors = followSet,
|
authors = followSet,
|
||||||
since = System.currentTimeMillis() / 1000 - (60 * 60 * 24 * 1), // 24 hours
|
limit = 200
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,8 +83,8 @@ object NostrHomeDataSource: NostrDataSource<Note>("HomeFeed") {
|
|||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
val newFollowAccountsFilter = createFollowAccountsFilter()
|
val newFollowAccountsFilter = createFollowAccountsFilter()
|
||||||
|
|
||||||
if (!equalAuthors(newFollowAccountsFilter, followAccountChannel.filter)) {
|
if (!equalAuthors(newFollowAccountsFilter, followAccountChannel.filter?.firstOrNull())) {
|
||||||
followAccountChannel.filter = newFollowAccountsFilter
|
followAccountChannel.filter = listOf(newFollowAccountsFilter).ifEmpty { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -4,33 +4,16 @@ import com.vitorpamplona.amethyst.model.Account
|
|||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import nostr.postr.JsonFilter
|
import nostr.postr.JsonFilter
|
||||||
|
|
||||||
object NostrNotificationDataSource: NostrDataSource<Note>("GlobalFeed") {
|
object NostrNotificationDataSource: NostrDataSource<Note>("NotificationFeed") {
|
||||||
lateinit var account: Account
|
lateinit var account: Account
|
||||||
|
|
||||||
fun createGlobalFilter() = JsonFilter(
|
fun createNotificationFilter() = JsonFilter(
|
||||||
since = System.currentTimeMillis() / 1000 - (60 * 60 * 24 * 7), // 7 days
|
tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)),
|
||||||
tags = mapOf("p" to listOf(account.userProfile().pubkeyHex).filterNotNull())
|
limit = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
val notificationChannel = requestNewChannel()
|
val notificationChannel = requestNewChannel()
|
||||||
|
|
||||||
fun <T> equalsIgnoreOrder(list1:List<T>?, list2:List<T>?): Boolean {
|
|
||||||
if (list1 == null && list2 == null) return true
|
|
||||||
if (list1 == null) return false
|
|
||||||
if (list2 == null) return false
|
|
||||||
|
|
||||||
return list1.size == list2.size && list1.toSet() == list2.toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun equalFilters(list1:JsonFilter?, list2:JsonFilter?): Boolean {
|
|
||||||
if (list1 == null && list2 == null) return true
|
|
||||||
if (list1 == null) return false
|
|
||||||
if (list2 == null) return false
|
|
||||||
|
|
||||||
return equalsIgnoreOrder(list1.tags?.get("p"), list2.tags?.get("p"))
|
|
||||||
&& equalsIgnoreOrder(list1.tags?.get("e"), list2.tags?.get("e"))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun feed(): List<Note> {
|
override fun feed(): List<Note> {
|
||||||
val set = account.userProfile().taggedPosts
|
val set = account.userProfile().taggedPosts
|
||||||
val filtered = synchronized(set) {
|
val filtered = synchronized(set) {
|
||||||
@@ -41,10 +24,6 @@ object NostrNotificationDataSource: NostrDataSource<Note>("GlobalFeed") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
val newFilter = createGlobalFilter()
|
notificationChannel.filter = listOf(createNotificationFilter()).ifEmpty { null }
|
||||||
|
|
||||||
if (!equalFilters(newFilter, notificationChannel.filter)) {
|
|
||||||
notificationChannel.filter = newFilter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -9,7 +9,7 @@ import nostr.postr.JsonFilter
|
|||||||
import nostr.postr.events.TextNoteEvent
|
import nostr.postr.events.TextNoteEvent
|
||||||
|
|
||||||
object NostrSingleEventDataSource: NostrDataSource<Note>("SingleEventFeed") {
|
object NostrSingleEventDataSource: NostrDataSource<Note>("SingleEventFeed") {
|
||||||
private var eventsToWatch = listOf<String>()
|
private var eventsToWatch = setOf<String>()
|
||||||
|
|
||||||
private fun createRepliesAndReactionsFilter(): JsonFilter? {
|
private fun createRepliesAndReactionsFilter(): JsonFilter? {
|
||||||
val reactionsToWatch = eventsToWatch.map { it.substring(0, 8) }
|
val reactionsToWatch = eventsToWatch.map { it.substring(0, 8) }
|
||||||
@@ -46,6 +46,7 @@ object NostrSingleEventDataSource: NostrDataSource<Note>("SingleEventFeed") {
|
|||||||
|
|
||||||
// downloads linked events to this event.
|
// downloads linked events to this event.
|
||||||
return JsonFilter(
|
return JsonFilter(
|
||||||
|
kinds = listOf(TextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind),
|
||||||
ids = interestedEvents
|
ids = interestedEvents
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -60,12 +61,16 @@ object NostrSingleEventDataSource: NostrDataSource<Note>("SingleEventFeed") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
repliesAndReactionsChannel.filter = createRepliesAndReactionsFilter()
|
val reactions = createRepliesAndReactionsFilter()
|
||||||
loadEventsChannel.filter = createLoadEventsIfNotLoadedFilter()
|
val missing = createLoadEventsIfNotLoadedFilter()
|
||||||
|
|
||||||
|
repliesAndReactionsChannel.filter = listOfNotNull(reactions).ifEmpty { null }
|
||||||
|
loadEventsChannel.filter = listOfNotNull(missing).ifEmpty { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(eventId: String) {
|
fun add(eventId: String) {
|
||||||
eventsToWatch = eventsToWatch.plus(eventId)
|
eventsToWatch = eventsToWatch.plus(eventId)
|
||||||
|
println("AAA: Event Watching ${eventsToWatch.size}")
|
||||||
resetFilters()
|
resetFilters()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,16 +7,19 @@ import nostr.postr.JsonFilter
|
|||||||
import nostr.postr.events.MetadataEvent
|
import nostr.postr.events.MetadataEvent
|
||||||
|
|
||||||
object NostrSingleUserDataSource: NostrDataSource<Note>("SingleUserFeed") {
|
object NostrSingleUserDataSource: NostrDataSource<Note>("SingleUserFeed") {
|
||||||
var usersToWatch = listOf<String>()
|
var usersToWatch = setOf<String>()
|
||||||
|
|
||||||
fun createUserFilter(): JsonFilter? {
|
fun createUserFilter(): List<JsonFilter>? {
|
||||||
if (usersToWatch.isEmpty()) return null
|
if (usersToWatch.isEmpty()) return null
|
||||||
|
|
||||||
return JsonFilter(
|
return usersToWatch.map {
|
||||||
|
JsonFilter(
|
||||||
kinds = listOf(MetadataEvent.kind),
|
kinds = listOf(MetadataEvent.kind),
|
||||||
authors = usersToWatch.map { it.substring(0, 8) }
|
authors = listOf(it.substring(0, 8)),
|
||||||
|
limit = 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val userChannel = requestNewChannel()
|
val userChannel = requestNewChannel()
|
||||||
|
|
||||||
@@ -32,6 +35,7 @@ object NostrSingleUserDataSource: NostrDataSource<Note>("SingleUserFeed") {
|
|||||||
|
|
||||||
fun add(userId: String) {
|
fun add(userId: String) {
|
||||||
usersToWatch = usersToWatch.plus(userId)
|
usersToWatch = usersToWatch.plus(userId)
|
||||||
|
println("AAA: User Watching ${usersToWatch.size}")
|
||||||
resetFilters()
|
resetFilters()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,8 +2,11 @@ package com.vitorpamplona.amethyst.service
|
|||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import nostr.postr.JsonFilter
|
import nostr.postr.JsonFilter
|
||||||
|
import nostr.postr.events.TextNoteEvent
|
||||||
|
|
||||||
object NostrThreadDataSource: NostrDataSource<Note>("SingleThreadFeed") {
|
object NostrThreadDataSource: NostrDataSource<Note>("SingleThreadFeed") {
|
||||||
val eventsToWatch = Collections.synchronizedList(mutableListOf<String>())
|
val eventsToWatch = Collections.synchronizedList(mutableListOf<String>())
|
||||||
@@ -16,6 +19,7 @@ object NostrThreadDataSource: NostrDataSource<Note>("SingleThreadFeed") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return JsonFilter(
|
return JsonFilter(
|
||||||
|
kinds = listOf(TextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind),
|
||||||
tags = mapOf("e" to reactionsToWatch)
|
tags = mapOf("e" to reactionsToWatch)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -46,8 +50,8 @@ object NostrThreadDataSource: NostrDataSource<Note>("SingleThreadFeed") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
repliesAndReactionsChannel.filter = createRepliesAndReactionsFilter()
|
repliesAndReactionsChannel.filter = listOfNotNull(createRepliesAndReactionsFilter()).ifEmpty { null }
|
||||||
loadEventsChannel.filter = createLoadEventsIfNotLoadedFilter()
|
loadEventsChannel.filter = listOfNotNull(createLoadEventsIfNotLoadedFilter()).ifEmpty { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadThread(noteId: String) {
|
fun loadThread(noteId: String) {
|
||||||
|
@@ -19,7 +19,7 @@ object NostrUserProfileDataSource: NostrDataSource<Note>("UserProfileFeed") {
|
|||||||
return JsonFilter(
|
return JsonFilter(
|
||||||
kinds = listOf(MetadataEvent.kind),
|
kinds = listOf(MetadataEvent.kind),
|
||||||
authors = listOf(user!!.pubkeyHex),
|
authors = listOf(user!!.pubkeyHex),
|
||||||
since = System.currentTimeMillis() / 1000 - (60 * 60 * 24 * 7)
|
limit = 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ object NostrUserProfileDataSource: NostrDataSource<Note>("UserProfileFeed") {
|
|||||||
return JsonFilter(
|
return JsonFilter(
|
||||||
kinds = listOf(TextNoteEvent.kind),
|
kinds = listOf(TextNoteEvent.kind),
|
||||||
authors = listOf(user!!.pubkeyHex),
|
authors = listOf(user!!.pubkeyHex),
|
||||||
since = System.currentTimeMillis() / 1000 - (60 * 60 * 24 * 4)
|
limit = 100
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ object NostrUserProfileDataSource: NostrDataSource<Note>("UserProfileFeed") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
userInfoChannel.filter = createUserInfoFilter()
|
userInfoChannel.filter = listOf(createUserInfoFilter()).ifEmpty { null }
|
||||||
notesChannel.filter = createUserPostsFilter()
|
notesChannel.filter = listOf(createUserPostsFilter()).ifEmpty { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -15,8 +15,7 @@ object NostrUserProfileFollowersDataSource: NostrDataSource<User>("UserProfileFo
|
|||||||
|
|
||||||
fun createFollowersFilter() = JsonFilter(
|
fun createFollowersFilter() = JsonFilter(
|
||||||
kinds = listOf(ContactListEvent.kind),
|
kinds = listOf(ContactListEvent.kind),
|
||||||
since = System.currentTimeMillis() / 1000 - (60 * 60 * 24 * 7), // 7 days
|
tags = mapOf("p" to listOf(user!!.pubkeyHex))
|
||||||
tags = mapOf("p" to listOf(user!!.pubkeyHex).filterNotNull())
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val followerChannel = requestNewChannel()
|
val followerChannel = requestNewChannel()
|
||||||
@@ -30,6 +29,6 @@ object NostrUserProfileFollowersDataSource: NostrDataSource<User>("UserProfileFo
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
followerChannel.filter = createFollowersFilter()
|
followerChannel.filter = listOf(createFollowersFilter()).ifEmpty { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -17,7 +17,7 @@ object NostrUserProfileFollowsDataSource: NostrDataSource<User>("UserProfileFoll
|
|||||||
return JsonFilter(
|
return JsonFilter(
|
||||||
kinds = listOf(ContactListEvent.kind),
|
kinds = listOf(ContactListEvent.kind),
|
||||||
authors = listOf(user!!.pubkeyHex),
|
authors = listOf(user!!.pubkeyHex),
|
||||||
since = System.currentTimeMillis() / 1000 - (60 * 60 * 24 * 7), // 4 days
|
limit = 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +28,6 @@ object NostrUserProfileFollowsDataSource: NostrDataSource<User>("UserProfileFoll
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
followChannel.filter = createFollowFilter()
|
followChannel.filter = listOf(createFollowFilter()).ifEmpty { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -31,6 +31,19 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrAccountDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrChatRoomDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrGlobalDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrNotificationDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrThreadDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrUserProfileFollowersDataSource
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrUserProfileFollowsDataSource
|
||||||
import com.vitorpamplona.amethyst.service.relays.Client
|
import com.vitorpamplona.amethyst.service.relays.Client
|
||||||
import com.vitorpamplona.amethyst.ui.screen.RelayPoolViewModel
|
import com.vitorpamplona.amethyst.ui.screen.RelayPoolViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -71,6 +84,23 @@ fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel)
|
|||||||
Client.allSubscriptions().map { "${it} ${Client.getSubscriptionFilters(it).joinToString { it.toJson() }}" }.forEach {
|
Client.allSubscriptions().map { "${it} ${Client.getSubscriptionFilters(it).joinToString { it.toJson() }}" }.forEach {
|
||||||
Log.d("CURRENT FILTERS", it)
|
Log.d("CURRENT FILTERS", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NostrAccountDataSource.printCounter()
|
||||||
|
NostrChannelDataSource.printCounter()
|
||||||
|
NostrChatRoomDataSource.printCounter()
|
||||||
|
NostrChatroomListDataSource.printCounter()
|
||||||
|
|
||||||
|
NostrGlobalDataSource.printCounter()
|
||||||
|
NostrHomeDataSource.printCounter()
|
||||||
|
NostrNotificationDataSource.printCounter()
|
||||||
|
|
||||||
|
NostrSingleEventDataSource.printCounter()
|
||||||
|
NostrSingleUserDataSource.printCounter()
|
||||||
|
NostrThreadDataSource.printCounter()
|
||||||
|
|
||||||
|
NostrUserProfileDataSource.printCounter()
|
||||||
|
NostrUserProfileFollowersDataSource.printCounter()
|
||||||
|
NostrUserProfileFollowsDataSource.printCounter()
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
|
@@ -107,13 +107,7 @@ fun NoteCompose(baseNote: Note, modifier: Modifier = Modifier, isInnerNote: Bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(start = if (!isInnerNote) 10.dp else 0.dp)
|
Column(modifier = Modifier.padding(start = if (!isInnerNote) 10.dp else 0.dp)) {
|
||||||
.clickable(onClick = {
|
|
||||||
note.let {
|
|
||||||
navController.navigate("Note/${note.idHex}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
if (author != null)
|
if (author != null)
|
||||||
UserDisplay(author)
|
UserDisplay(author)
|
||||||
|
@@ -6,12 +6,14 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import java.lang.System.currentTimeMillis
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController) {
|
fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
|
@@ -293,7 +293,7 @@ private fun MessageButton(user: User, navController: NavController) {
|
|||||||
painter = painterResource(R.drawable.ic_dm),
|
painter = painterResource(R.drawable.ic_dm),
|
||||||
"Send a Direct Message",
|
"Send a Direct Message",
|
||||||
modifier = Modifier.size(20.dp),
|
modifier = Modifier.size(20.dp),
|
||||||
tint = Color.Unspecified
|
tint = Color.White
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user