Improving performance with more precise filters.

This commit is contained in:
Vitor Pamplona
2023-01-18 11:44:31 -05:00
parent f2b913f7c8
commit 7d18f36e3e
22 changed files with 163 additions and 141 deletions

View File

@@ -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()

View File

@@ -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)
} }

View File

@@ -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)

View File

@@ -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
} }

View File

@@ -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 }
}
} }
} }

View File

@@ -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 }
} }
} }

View File

@@ -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 }
} }
} }

View File

@@ -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 }
} }
} }

View File

@@ -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)
} }
} }
} }

View File

@@ -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
}
} }
} }

View File

@@ -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 }
} }
} }
} }

View File

@@ -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
}
} }
} }

View File

@@ -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()
} }

View File

@@ -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()
} }

View File

@@ -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) {

View File

@@ -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 }
} }
} }

View File

@@ -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 }
} }
} }

View File

@@ -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 }
} }
} }

View File

@@ -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(

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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
) )
} }
} }