From bd94544c9b810829f8187137144759c4bf7a2e51 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sun, 5 Feb 2023 18:14:41 -0500 Subject: [PATCH] Breaking filters down by feed type: Home, DMs, Public Chats and Global. --- .../amethyst/LocalPreferences.kt | 15 +- .../vitorpamplona/amethyst/ServiceManager.kt | 6 +- .../vitorpamplona/amethyst/model/Account.kt | 27 +- .../com/vitorpamplona/amethyst/model/User.kt | 3 + .../vitorpamplona/amethyst/service/Channel.kt | 3 +- .../amethyst/service/Constants.kt | 27 -- .../service/NostrAccountDataSource.kt | 48 ++-- .../service/NostrChannelDataSource.kt | 15 +- .../service/NostrChatRoomDataSource.kt | 28 +- .../service/NostrChatroomListDataSource.kt | 64 +++-- .../amethyst/service/NostrDataSource.kt | 12 +- .../amethyst/service/NostrGlobalDataSource.kt | 11 +- .../amethyst/service/NostrHomeDataSource.kt | 37 +-- .../NostrSearchEventOrUserDataSource.kt | 16 +- .../service/NostrSingleChannelDataSource.kt | 24 +- .../service/NostrSingleEventDataSource.kt | 40 +-- .../service/NostrSingleUserDataSource.kt | 26 +- .../amethyst/service/NostrThreadDataSource.kt | 22 +- .../service/NostrUserProfileDataSource.kt | 52 ++-- .../amethyst/service/relays/Client.kt | 17 +- .../amethyst/service/relays/Constants.kt | 48 ++++ .../amethyst/service/relays/Relay.kt | 15 +- .../amethyst/service/relays/RelayPool.kt | 5 +- .../amethyst/service/relays/TypedFilter.kt | 8 + .../amethyst/ui/actions/NewRelayListView.kt | 247 ++++++++++++------ .../ui/actions/NewRelayListViewModel.kt | 63 ++++- .../amethyst/ui/navigation/AppTopBar.kt | 2 +- 27 files changed, 575 insertions(+), 306 deletions(-) delete mode 100644 app/src/main/java/com/vitorpamplona/amethyst/service/Constants.kt create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/service/relays/Constants.kt create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/service/relays/TypedFilter.kt diff --git a/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt index 952d9fef0..227ad0d76 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt @@ -1,17 +1,23 @@ package com.vitorpamplona.amethyst import android.content.Context +import com.google.gson.GsonBuilder +import com.google.gson.reflect.TypeToken import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.DefaultChannels import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.toByteArray +import com.vitorpamplona.amethyst.ui.actions.NewRelayListViewModel import com.vitorpamplona.amethyst.ui.navigation.Route import nostr.postr.Persona +import nostr.postr.events.ContactListEvent +import nostr.postr.events.Event import nostr.postr.toHex class LocalPreferences(context: Context) { val encryptedPreferences = EncryptedStorage().preferences(context) + val gson = GsonBuilder().create() fun clearEncryptedStorage() { encryptedPreferences.edit().apply { @@ -19,6 +25,7 @@ class LocalPreferences(context: Context) { remove("nostr_pubkey") remove("following_channels") remove("hidden_users") + remove("relays") }.apply() } @@ -28,6 +35,7 @@ class LocalPreferences(context: Context) { account.loggedIn.pubKey.let { putString("nostr_pubkey", it.toHex()) } account.followingChannels.let { putStringSet("following_channels", it) } account.hiddenUsers.let { putStringSet("hidden_users", it) } + account.localRelays.let { putString("relays", gson.toJson(it)) } }.apply() } @@ -37,12 +45,17 @@ class LocalPreferences(context: Context) { val pubKey = getString("nostr_pubkey", null) val followingChannels = getStringSet("following_channels", null)?.toMutableSet() ?: mutableSetOf() val hiddenUsers = getStringSet("hidden_users", emptySet())?.toMutableSet() ?: mutableSetOf() + val localRelays = gson.fromJson( + getString("relays", "[]"), + object : TypeToken>() {}.type + ) ?: setOf() if (pubKey != null) { return Account( Persona(privKey = privKey?.toByteArray(), pubKey = pubKey.toByteArray()), followingChannels, - hiddenUsers + hiddenUsers, + localRelays ) } else { return null diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt b/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt index 03f8d0b14..3c025176f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ServiceManager.kt @@ -1,7 +1,7 @@ package com.vitorpamplona.amethyst import com.vitorpamplona.amethyst.model.Account -import com.vitorpamplona.amethyst.service.Constants +import com.vitorpamplona.amethyst.service.relays.Constants import com.vitorpamplona.amethyst.service.NostrAccountDataSource import com.vitorpamplona.amethyst.service.NostrChannelDataSource import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource @@ -29,7 +29,7 @@ object ServiceManager { val myAccount = account if (myAccount != null) { - Client.connect(myAccount.activeRelays() ?: Constants.defaultRelays) + Client.connect(myAccount.convertLocalRelays()) // start services NostrAccountDataSource.account = myAccount @@ -55,7 +55,7 @@ object ServiceManager { NostrChatroomListDataSource.start() } else { // if not logged in yet, start a basic service wit default relays - Client.connect() + Client.connect(Constants.convertDefaultRelays()) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index e831f25c6..702cdd4eb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -1,7 +1,7 @@ package com.vitorpamplona.amethyst.model import androidx.lifecycle.LiveData -import com.vitorpamplona.amethyst.service.Constants +import com.vitorpamplona.amethyst.service.relays.Constants import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent @@ -9,8 +9,10 @@ import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.ReportEvent import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.relays.Client +import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.service.relays.RelayPool +import com.vitorpamplona.amethyst.ui.actions.NewRelayListViewModel import java.util.Date import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -34,8 +36,9 @@ val DefaultChannels = setOf( class Account( val loggedIn: Persona, - var followingChannels: Set = DefaultChannels.toMutableSet(), - var hiddenUsers: Set = mutableSetOf() + var followingChannels: Set = DefaultChannels, + var hiddenUsers: Set = setOf(), + var localRelays: Set = Constants.defaultRelays.toSet() ) { fun userProfile(): User { @@ -330,14 +333,23 @@ class Account( } fun activeRelays(): Array? { - return userProfile().relays?.map { Relay(it.key, it.value.read, it.value.write) }?.toTypedArray() + return userProfile().relays?.map { + val localFeedTypes = localRelays.firstOrNull() { localRelay -> localRelay.url == it.key }?.feedTypes ?: FeedType.values().toSet() + Relay(it.key, it.value.read, it.value.write, localFeedTypes) + }?.toTypedArray() + } + + fun convertLocalRelays(): Array { + return localRelays.map { + Relay(it.url, it.read, it.write, it.feedTypes) + }.toTypedArray() } init { userProfile().subscribe(object: User.Listener() { override fun onRelayChange() { Client.disconnect() - Client.connect(activeRelays() ?: Constants.defaultRelays) + Client.connect(activeRelays() ?: convertLocalRelays()) RelayPool.requestAndWatch() } }) @@ -396,6 +408,11 @@ class Account( innerReports).toSet() } + fun saveRelayList(value: List) { + localRelays = value.toSet() + sendNewRelayList(value.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) } ) + } + } class AccountLiveData(private val account: Account): LiveData(AccountState(account)) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt index 93d70cabd..9faf41648 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -4,6 +4,7 @@ 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.FeedType import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.note.toShortenHex import fr.acinq.secp256k1.Hex @@ -178,6 +179,8 @@ class User(val pubkeyHex: String) { updatedFollowsAt = updateAt } + data class RelayMetadata(val read: Boolean, val write: Boolean, val activeTypes: Set) + fun updateRelays(relayUse: Map) { if (relays != relayUse) { relays = relayUse diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/Channel.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/Channel.kt index 45ef4a7a6..748e28608 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/Channel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/Channel.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.service +import com.vitorpamplona.amethyst.service.relays.TypedFilter import java.util.UUID import nostr.postr.JsonFilter @@ -7,7 +8,7 @@ data class Channel ( val id: String = UUID.randomUUID().toString().substring(0,4), val onEOSE: ((Long) -> Unit)? = null ) { - var filter: List? = null // Inactive when null + var filter: List? = null // Inactive when null fun updateEOSE(l: Long) { onEOSE?.let { it(l) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/Constants.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/Constants.kt deleted file mode 100644 index 14430b13a..000000000 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/Constants.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.vitorpamplona.amethyst.service - -import com.vitorpamplona.amethyst.service.relays.Relay - -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://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.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://no.str.cr", read = true, write = true), - Relay("wss://nos.lol", read = true, write = true) - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt index 9f9171004..1d39be607 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt @@ -6,6 +6,8 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.UserState import com.vitorpamplona.amethyst.service.model.ReportEvent import com.vitorpamplona.amethyst.service.model.RepostEvent +import com.vitorpamplona.amethyst.service.relays.FeedType +import com.vitorpamplona.amethyst.service.relays.TypedFilter import nostr.postr.JsonFilter import nostr.postr.events.ContactListEvent import nostr.postr.events.MetadataEvent @@ -14,32 +16,44 @@ import nostr.postr.events.TextNoteEvent object NostrAccountDataSource: NostrDataSource("AccountData") { lateinit var account: Account - fun createAccountContactListFilter(): JsonFilter { - return JsonFilter( - kinds = listOf(ContactListEvent.kind), - authors = listOf(account.userProfile().pubkeyHex), - limit = 1 + fun createAccountContactListFilter(): TypedFilter { + return TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + kinds = listOf(ContactListEvent.kind), + authors = listOf(account.userProfile().pubkeyHex), + limit = 1 + ) ) } - fun createAccountMetadataFilter(): JsonFilter { - return JsonFilter( - kinds = listOf(MetadataEvent.kind), - authors = listOf(account.userProfile().pubkeyHex), - limit = 1 + fun createAccountMetadataFilter(): TypedFilter { + return TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + kinds = listOf(MetadataEvent.kind), + authors = listOf(account.userProfile().pubkeyHex), + limit = 1 + ) ) } - fun createAccountReportsFilter(): JsonFilter { - return JsonFilter( - kinds = listOf(ReportEvent.kind), - authors = listOf(account.userProfile().pubkeyHex) + fun createAccountReportsFilter(): TypedFilter { + return TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + kinds = listOf(ReportEvent.kind), + authors = listOf(account.userProfile().pubkeyHex) + ) ) } - fun createNotificationFilter() = JsonFilter( - tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)), - limit = 200 + fun createNotificationFilter() = TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)), + limit = 200 + ) ) val accountChannel = requestNewChannel() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChannelDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChannelDataSource.kt index bd2ead525..ff483434e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChannelDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChannelDataSource.kt @@ -4,6 +4,8 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent +import com.vitorpamplona.amethyst.service.relays.FeedType +import com.vitorpamplona.amethyst.service.relays.TypedFilter import nostr.postr.JsonFilter object NostrChannelDataSource: NostrDataSource("ChatroomFeed") { @@ -15,12 +17,15 @@ object NostrChannelDataSource: NostrDataSource("ChatroomFeed") { resetFilters() } - fun createMessagesToChannelFilter(): JsonFilter? { + fun createMessagesToChannelFilter(): TypedFilter? { if (channel != null) { - return JsonFilter( - kinds = listOf(ChannelMessageEvent.kind), - tags = mapOf("e" to listOfNotNull(channel?.idHex)), - limit = 200 + return TypedFilter( + types = setOf(FeedType.PUBLIC_CHATS), + filter = JsonFilter( + kinds = listOf(ChannelMessageEvent.kind), + tags = mapOf("e" to listOfNotNull(channel?.idHex)), + limit = 200 + ) ) } return null diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatRoomDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatRoomDataSource.kt index bb1ebb883..8d8d30083 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatRoomDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatRoomDataSource.kt @@ -4,6 +4,8 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.service.relays.FeedType +import com.vitorpamplona.amethyst.service.relays.TypedFilter import nostr.postr.JsonFilter import nostr.postr.events.PrivateDmEvent @@ -16,28 +18,34 @@ object NostrChatRoomDataSource: NostrDataSource("ChatroomFeed") { withUser = LocalCache.users[userId] } - fun createMessagesToMeFilter(): JsonFilter? { + fun createMessagesToMeFilter(): TypedFilter? { val myPeer = withUser return if (myPeer != null) { - JsonFilter( - kinds = listOf(PrivateDmEvent.kind), - authors = listOf(myPeer.pubkeyHex) , - tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)) + TypedFilter( + types = setOf(FeedType.PRIVATE_DMS), + filter = JsonFilter( + kinds = listOf(PrivateDmEvent.kind), + authors = listOf(myPeer.pubkeyHex) , + tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)) + ) ) } else { null } } - fun createMessagesFromMeFilter(): JsonFilter? { + fun createMessagesFromMeFilter(): TypedFilter? { val myPeer = withUser return if (myPeer != null) { - JsonFilter( - kinds = listOf(PrivateDmEvent.kind), - authors = listOf(account.userProfile().pubkeyHex), - tags = mapOf("p" to listOf(myPeer.pubkeyHex)) + TypedFilter( + types = setOf(FeedType.PUBLIC_CHATS), + filter = JsonFilter( + kinds = listOf(PrivateDmEvent.kind), + authors = listOf(account.userProfile().pubkeyHex), + tags = mapOf("p" to listOf(myPeer.pubkeyHex)) + ) ) } else { null diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomListDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomListDataSource.kt index ed69fe3b9..6cc180b48 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomListDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrChatroomListDataSource.kt @@ -6,48 +6,68 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent +import com.vitorpamplona.amethyst.service.relays.FeedType +import com.vitorpamplona.amethyst.service.relays.TypedFilter import nostr.postr.JsonFilter import nostr.postr.events.PrivateDmEvent object NostrChatroomListDataSource: NostrDataSource("MailBoxFeed") { lateinit var account: Account - fun createMessagesToMeFilter() = JsonFilter( - kinds = listOf(PrivateDmEvent.kind), - tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)) + fun createMessagesToMeFilter() = TypedFilter( + types = setOf(FeedType.PRIVATE_DMS), + filter = JsonFilter( + kinds = listOf(PrivateDmEvent.kind), + tags = mapOf("p" to listOf(account.userProfile().pubkeyHex)) + ) ) - fun createMessagesFromMeFilter() = JsonFilter( - kinds = listOf(PrivateDmEvent.kind), - authors = listOf(account.userProfile().pubkeyHex) + fun createMessagesFromMeFilter() = TypedFilter( + types = setOf(FeedType.PRIVATE_DMS), + filter = JsonFilter( + kinds = listOf(PrivateDmEvent.kind), + authors = listOf(account.userProfile().pubkeyHex) + ) ) - fun createChannelsCreatedbyMeFilter() = JsonFilter( - kinds = listOf(ChannelCreateEvent.kind, ChannelMetadataEvent.kind), - authors = listOf(account.userProfile().pubkeyHex) + fun createChannelsCreatedbyMeFilter() = TypedFilter( + types = setOf(FeedType.PUBLIC_CHATS), + filter = JsonFilter( + kinds = listOf(ChannelCreateEvent.kind, ChannelMetadataEvent.kind), + authors = listOf(account.userProfile().pubkeyHex) + ) ) - fun createMyChannelsFilter() = JsonFilter( - kinds = listOf(ChannelCreateEvent.kind), - ids = account.followingChannels.toList() + fun createMyChannelsFilter() = TypedFilter( + types = setOf(FeedType.PUBLIC_CHATS), + filter = JsonFilter( + kinds = listOf(ChannelCreateEvent.kind), + ids = account.followingChannels.toList() + ) ) - fun createLastChannelInfoFilter(): List { + fun createLastChannelInfoFilter(): List { return account.followingChannels.map { - JsonFilter( - kinds = listOf(ChannelMetadataEvent.kind), - tags = mapOf("e" to listOf(it)), - limit = 1 + TypedFilter( + types = setOf(FeedType.PUBLIC_CHATS), + filter = JsonFilter( + kinds = listOf(ChannelMetadataEvent.kind), + tags = mapOf("e" to listOf(it)), + limit = 1 + ) ) } } - fun createLastMessageOfEachChannelFilter(): List { + fun createLastMessageOfEachChannelFilter(): List { return account.followingChannels.map { - JsonFilter( - kinds = listOf(ChannelMessageEvent.kind), - tags = mapOf("e" to listOf(it)), - limit = 1 + TypedFilter( + types = setOf(FeedType.PUBLIC_CHATS), + filter = JsonFilter( + kinds = listOf(ChannelMessageEvent.kind), + tags = mapOf("e" to listOf(it)), + limit = 1 + ) ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt index 5e81e6a73..b2ef9ed72 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt @@ -1,8 +1,5 @@ package com.vitorpamplona.amethyst.service -import android.os.Handler -import android.os.Looper -import android.util.Log import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.UrlCachedPreviewer @@ -16,11 +13,8 @@ import com.vitorpamplona.amethyst.service.model.ReportEvent import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.relays.Client import com.vitorpamplona.amethyst.service.relays.Relay -import java.util.Collections import java.util.Date import java.util.UUID -import kotlin.time.ExperimentalTime -import kotlin.time.measureTimedValue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -181,7 +175,7 @@ abstract class NostrDataSource(val debugName: String) { // saves the channels that are currently active 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() } } + val currentFilter = activeChannels.associate { it.id to it.filter!!.joinToString("|") { it.filter.toJson() } } updateChannelFilters() @@ -195,7 +189,7 @@ abstract class NostrDataSource(val debugName: String) { Client.close(channel.id) } else { // was active and is still active, check if it has changed. - if (channelsNewFilter.joinToString("|") { it.toJson() } != currentFilter[channel.id]) { + if (channelsNewFilter.joinToString("|") { it.filter.toJson() } != currentFilter[channel.id]) { Client.close(channel.id) Client.sendFilter(channel.id, channelsNewFilter) } else { @@ -208,7 +202,7 @@ abstract class NostrDataSource(val debugName: String) { // was not active and is still not active, does nothing } else { // was not active and becomes active, sends the filter. - if (channelsNewFilter.joinToString("|") { it.toJson() } != currentFilter[channel.id]) { + if (channelsNewFilter.joinToString("|") { it.filter.toJson() } != currentFilter[channel.id]) { Client.sendFilter(channel.id, channelsNewFilter) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGlobalDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGlobalDataSource.kt index 7ba5c80f1..588d96148 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGlobalDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGlobalDataSource.kt @@ -4,14 +4,19 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent +import com.vitorpamplona.amethyst.service.relays.FeedType +import com.vitorpamplona.amethyst.service.relays.TypedFilter import nostr.postr.JsonFilter import nostr.postr.events.TextNoteEvent object NostrGlobalDataSource: NostrDataSource("GlobalFeed") { lateinit var account: Account - fun createGlobalFilter() = JsonFilter( - kinds = listOf(TextNoteEvent.kind, ChannelMessageEvent.kind), - limit = 200 + fun createGlobalFilter() = TypedFilter( + types = setOf(FeedType.GLOBAL), + filter = JsonFilter( + kinds = listOf(TextNoteEvent.kind, ChannelMessageEvent.kind), + limit = 200 + ) ) val globalFeedChannel = requestNewChannel() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt index aaec3293c..cc70cfe09 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt @@ -5,6 +5,8 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.model.RepostEvent +import com.vitorpamplona.amethyst.service.relays.FeedType +import com.vitorpamplona.amethyst.service.relays.TypedFilter import nostr.postr.JsonFilter import nostr.postr.events.TextNoteEvent import nostr.postr.toHex @@ -30,7 +32,7 @@ object NostrHomeDataSource: NostrDataSource("HomeFeed") { account.userProfile().unsubscribe(cacheListener) } - fun createFollowAccountsFilter(): JsonFilter { + fun createFollowAccountsFilter(): TypedFilter { val follows = account.userProfile().follows val followKeys = follows.map { @@ -39,31 +41,18 @@ object NostrHomeDataSource: NostrDataSource("HomeFeed") { val followSet = followKeys.plus(account.userProfile().pubkeyHex.substring(0, 6)) - return JsonFilter( - kinds = listOf(TextNoteEvent.kind, RepostEvent.kind), - authors = followSet, - limit = 400 + return TypedFilter( + types = setOf(FeedType.FOLLOWS), + filter = JsonFilter( + kinds = listOf(TextNoteEvent.kind, RepostEvent.kind), + authors = followSet, + limit = 400 + ) ) } val followAccountChannel = requestNewChannel() - fun equalsIgnoreOrder(list1:List?, list2:List?): 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 equalAuthors(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.authors, list2.authors) - } - override fun feed(): List { val user = account.userProfile() @@ -75,10 +64,6 @@ object NostrHomeDataSource: NostrDataSource("HomeFeed") { } override fun updateChannelFilters() { - val newFollowAccountsFilter = createFollowAccountsFilter() - - if (!equalAuthors(newFollowAccountsFilter, followAccountChannel.filter?.firstOrNull())) { - followAccountChannel.filter = listOf(newFollowAccountsFilter).ifEmpty { null } - } + followAccountChannel.filter = listOf(createFollowAccountsFilter()).ifEmpty { null } } } \ No newline at end of file diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSearchEventOrUserDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSearchEventOrUserDataSource.kt index 9bbcf33a2..cd1f1bdb2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSearchEventOrUserDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSearchEventOrUserDataSource.kt @@ -5,6 +5,8 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.decodePublicKey import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.RepostEvent +import com.vitorpamplona.amethyst.service.relays.FeedType +import com.vitorpamplona.amethyst.service.relays.TypedFilter import java.util.Collections import nostr.postr.JsonFilter import nostr.postr.bechToBytes @@ -14,19 +16,23 @@ import nostr.postr.toHex object NostrSearchEventOrUserDataSource: NostrDataSource("SingleEventFeed") { private var hexToWatch: String? = null - private fun createAnythingWithIDFilter(): List? { + private fun createAnythingWithIDFilter(): List? { if (hexToWatch == null) { return null } // downloads all the reactions to a given event. return listOf( - JsonFilter( + TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( ids = listOfNotNull(hexToWatch) - ), - JsonFilter( + )), + TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( authors = listOfNotNull(hexToWatch) - ) + )) ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleChannelDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleChannelDataSource.kt index 9edab9c30..243d3e7a7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleChannelDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleChannelDataSource.kt @@ -7,6 +7,8 @@ import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.RepostEvent +import com.vitorpamplona.amethyst.service.relays.FeedType +import com.vitorpamplona.amethyst.service.relays.TypedFilter import java.util.Collections import nostr.postr.JsonFilter import nostr.postr.events.TextNoteEvent @@ -14,7 +16,7 @@ import nostr.postr.events.TextNoteEvent object NostrSingleChannelDataSource: NostrDataSource("SingleChannelFeed") { private var channelsToWatch = setOf() - private fun createRepliesAndReactionsFilter(): JsonFilter? { + private fun createRepliesAndReactionsFilter(): TypedFilter? { val reactionsToWatch = channelsToWatch.map { it } if (reactionsToWatch.isEmpty()) { @@ -22,13 +24,16 @@ object NostrSingleChannelDataSource: NostrDataSource("SingleChannelFeed") } // downloads all the reactions to a given event. - return JsonFilter( - kinds = listOf(ChannelMetadataEvent.kind), - tags = mapOf("e" to reactionsToWatch) + return TypedFilter( + types = setOf(FeedType.PUBLIC_CHATS), + filter = JsonFilter( + kinds = listOf(ChannelMetadataEvent.kind), + tags = mapOf("e" to reactionsToWatch) + ) ) } - fun createLoadEventsIfNotLoadedFilter(): JsonFilter? { + fun createLoadEventsIfNotLoadedFilter(): TypedFilter? { val directEventsToLoad = channelsToWatch .map { LocalCache.getOrCreateChannel(it) } .filter { it.notes.isEmpty() } @@ -40,9 +45,12 @@ object NostrSingleChannelDataSource: NostrDataSource("SingleChannelFeed") } // downloads linked events to this event. - return JsonFilter( - kinds = listOf(ChannelCreateEvent.kind), - ids = interestedEvents.toList() + return TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + kinds = listOf(ChannelCreateEvent.kind), + ids = interestedEvents.toList() + ) ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt index 29f412f5e..6dc2d401d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt @@ -8,6 +8,8 @@ import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.ReportEvent import com.vitorpamplona.amethyst.service.model.RepostEvent +import com.vitorpamplona.amethyst.service.relays.FeedType +import com.vitorpamplona.amethyst.service.relays.TypedFilter import java.util.Collections import java.util.Date import nostr.postr.JsonFilter @@ -17,7 +19,7 @@ import nostr.postr.events.TextNoteEvent object NostrSingleEventDataSource: NostrDataSource("SingleEventFeed") { private var eventsToWatch = setOf() - private fun createRepliesAndReactionsFilter(): List? { + private fun createRepliesAndReactionsFilter(): List? { val reactionsToWatch = eventsToWatch.map { LocalCache.getOrCreateNote(it) } if (reactionsToWatch.isEmpty()) { @@ -30,17 +32,20 @@ object NostrSingleEventDataSource: NostrDataSource("SingleEventFeed") { 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.idHex)), - since = it.lastReactionsDownloadTime + TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + kinds = listOf( + TextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind, ReportEvent.kind + ), + tags = mapOf("e" to listOf(it.idHex)), + since = it.lastReactionsDownloadTime + ) ) } } - fun createLoadEventsIfNotLoadedFilter(): List? { + fun createLoadEventsIfNotLoadedFilter(): List? { val directEventsToLoad = eventsToWatch .map { LocalCache.getOrCreateNote(it) } .filter { it.event == null } @@ -60,13 +65,18 @@ object NostrSingleEventDataSource: NostrDataSource("SingleEventFeed") { } // downloads linked events to this event. - return listOf(JsonFilter( - kinds = listOf( - TextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind, - ChannelMessageEvent.kind, ChannelCreateEvent.kind, ChannelMetadataEvent.kind - ), - ids = interestedEvents.toList() - )) + return listOf( + TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + kinds = listOf( + TextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind, + ChannelMessageEvent.kind, ChannelCreateEvent.kind, ChannelMetadataEvent.kind + ), + ids = interestedEvents.toList() + ) + ) + ) } val singleEventChannel = requestNewChannel() { time -> diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt index 04f838a3e..f588bd8e2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt @@ -4,6 +4,8 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.model.ReportEvent +import com.vitorpamplona.amethyst.service.relays.FeedType +import com.vitorpamplona.amethyst.service.relays.TypedFilter import java.util.Collections import nostr.postr.JsonFilter import nostr.postr.events.MetadataEvent @@ -11,25 +13,31 @@ import nostr.postr.events.MetadataEvent object NostrSingleUserDataSource: NostrDataSource("SingleUserFeed") { var usersToWatch = setOf() - fun createUserFilter(): List? { + fun createUserFilter(): List? { if (usersToWatch.isEmpty()) return null return usersToWatch.filter { LocalCache.getOrCreateUser(it).latestMetadata == null }.map { - JsonFilter( - kinds = listOf(MetadataEvent.kind), - authors = listOf(it), - limit = 1 + TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + kinds = listOf(MetadataEvent.kind), + authors = listOf(it), + limit = 1 + ) ) } } - fun createUserReportFilter(): List? { + fun createUserReportFilter(): List? { if (usersToWatch.isEmpty()) return null return usersToWatch.map { - JsonFilter( - kinds = listOf(ReportEvent.kind), - tags = mapOf("p" to listOf(it)) + TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + kinds = listOf(ReportEvent.kind), + tags = mapOf("p" to listOf(it)) + ) ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrThreadDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrThreadDataSource.kt index 634522c11..ca54e20fd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrThreadDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrThreadDataSource.kt @@ -4,6 +4,8 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.RepostEvent +import com.vitorpamplona.amethyst.service.relays.FeedType +import com.vitorpamplona.amethyst.service.relays.TypedFilter import java.util.Collections import nostr.postr.JsonFilter import nostr.postr.events.TextNoteEvent @@ -11,18 +13,21 @@ import nostr.postr.events.TextNoteEvent object NostrThreadDataSource: NostrDataSource("SingleThreadFeed") { private var eventsToWatch = setOf() - fun createRepliesAndReactionsFilter(): JsonFilter? { + fun createRepliesAndReactionsFilter(): TypedFilter? { if (eventsToWatch.isEmpty()) { return null } - return JsonFilter( - kinds = listOf(TextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind), - tags = mapOf("e" to eventsToWatch.toList()) + return TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + kinds = listOf(TextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind), + tags = mapOf("e" to eventsToWatch.toList()) + ) ) } - fun createLoadEventsIfNotLoadedFilter(): JsonFilter? { + fun createLoadEventsIfNotLoadedFilter(): TypedFilter? { val nodes = eventsToWatch.map { LocalCache.getOrCreateNote(it) } val eventsToLoad = nodes @@ -33,8 +38,11 @@ object NostrThreadDataSource: NostrDataSource("SingleThreadFeed") { return null } - return JsonFilter( - ids = eventsToLoad + return TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + ids = eventsToLoad + ) ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt index a77bd2c1f..6971a5c21 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt @@ -5,6 +5,8 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.toByteArray +import com.vitorpamplona.amethyst.service.relays.FeedType +import com.vitorpamplona.amethyst.service.relays.TypedFilter import nostr.postr.JsonFilter import nostr.postr.events.ContactListEvent import nostr.postr.events.MetadataEvent @@ -19,33 +21,45 @@ object NostrUserProfileDataSource: NostrDataSource("UserProfileFeed") { resetFilters() } - fun createUserInfoFilter(): JsonFilter { - return JsonFilter( - kinds = listOf(MetadataEvent.kind), - authors = listOf(user!!.pubkeyHex), - limit = 1 + fun createUserInfoFilter(): TypedFilter { + return TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + kinds = listOf(MetadataEvent.kind), + authors = listOf(user!!.pubkeyHex), + limit = 1 + ) ) } - fun createUserPostsFilter(): JsonFilter { - return JsonFilter( - kinds = listOf(TextNoteEvent.kind), - authors = listOf(user!!.pubkeyHex), - limit = 100 + fun createUserPostsFilter(): TypedFilter { + return TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + kinds = listOf(TextNoteEvent.kind), + authors = listOf(user!!.pubkeyHex), + limit = 100 + ) ) } - fun createFollowFilter(): JsonFilter { - return JsonFilter( + fun createFollowFilter(): TypedFilter { + return TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + kinds = listOf(ContactListEvent.kind), + authors = listOf(user!!.pubkeyHex), + limit = 1 + ) + ) + } + + fun createFollowersFilter() = TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( kinds = listOf(ContactListEvent.kind), - authors = listOf(user!!.pubkeyHex), - limit = 1 + tags = mapOf("p" to listOf(user!!.pubkeyHex)) ) - } - - fun createFollowersFilter() = JsonFilter( - kinds = listOf(ContactListEvent.kind), - tags = mapOf("p" to listOf(user!!.pubkeyHex)) ) val userInfoChannel = requestNewChannel() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt index b5f6ae751..108569f3a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt @@ -1,10 +1,6 @@ package com.vitorpamplona.amethyst.service.relays -import com.vitorpamplona.amethyst.service.Constants -import java.util.Collections import java.util.UUID -import java.util.concurrent.ConcurrentHashMap -import nostr.postr.JsonFilter import nostr.postr.events.Event /** @@ -24,10 +20,10 @@ object Client: RelayPool.Listener { **/ var lenient: Boolean = false private var listeners = setOf() - private var relays = Constants.defaultRelays - private var subscriptions = mapOf>() + private var relays = Constants.convertDefaultRelays() + private var subscriptions = mapOf>() - fun connect(relays: Array = Constants.defaultRelays) { + fun connect(relays: Array) { RelayPool.register(this) RelayPool.unloadRelays() RelayPool.loadRelays(relays.toList()) @@ -36,14 +32,15 @@ object Client: RelayPool.Listener { fun sendFilter( subscriptionId: String = UUID.randomUUID().toString().substring(0..10), - filters: List = listOf(JsonFilter()) + filters: List = listOf() ) { subscriptions = subscriptions + Pair(subscriptionId, filters) RelayPool.sendFilter(subscriptionId) } + fun sendFilterOnlyIfDisconnected( subscriptionId: String = UUID.randomUUID().toString().substring(0..10), - filters: List = listOf(JsonFilter()) + filters: List = listOf() ) { subscriptions = subscriptions + Pair(subscriptionId, filters) RelayPool.sendFilterOnlyIfDisconnected() @@ -91,7 +88,7 @@ object Client: RelayPool.Listener { return subscriptions.keys.toList() } - fun getSubscriptionFilters(subId: String): List { + fun getSubscriptionFilters(subId: String): List { return subscriptions[subId] ?: emptyList() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Constants.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Constants.kt new file mode 100644 index 000000000..611aaa015 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Constants.kt @@ -0,0 +1,48 @@ +package com.vitorpamplona.amethyst.service.relays + +import com.vitorpamplona.amethyst.ui.actions.NewRelayListViewModel + +object Constants { + val activeTypes = setOf(FeedType.FOLLOWS, FeedType.PUBLIC_CHATS, FeedType.PRIVATE_DMS) + val activeTypesGlobal = setOf(FeedType.FOLLOWS, FeedType.PUBLIC_CHATS, FeedType.PRIVATE_DMS, FeedType.GLOBAL) + + fun convertDefaultRelays(): Array { + return defaultRelays.map { + Relay(it.url, it.read, it.write, it.feedTypes) + }.toTypedArray() + } + + val defaultRelays = arrayOf( + // Free relays + NewRelayListViewModel.Relay("wss://nostr.bitcoiner.social", read = true, write = true, feedTypes = activeTypes), + NewRelayListViewModel.Relay("wss://relay.nostr.bg", read = true, write = true, feedTypes = activeTypes), + NewRelayListViewModel.Relay("wss://brb.io", read = true, write = true, feedTypes = activeTypes), + NewRelayListViewModel.Relay("wss://relay.snort.social", read = true, write = true, feedTypes = activeTypes), + NewRelayListViewModel.Relay("wss://relay.damus.io", read = true, write = true, feedTypes = activeTypes), + NewRelayListViewModel.Relay("wss://nostr.oxtr.dev", read = true, write = true, feedTypes = activeTypes), + NewRelayListViewModel.Relay("wss://nostr-pub.wellorder.net", read = true, write = true, feedTypes = activeTypes), + NewRelayListViewModel.Relay("wss://nostr.mom", read = true, write = true, feedTypes = activeTypes), + NewRelayListViewModel.Relay("wss://no.str.cr", read = true, write = true, feedTypes = activeTypes), + NewRelayListViewModel.Relay("wss://nos.lol", read = true, write = true, feedTypes = activeTypes), + + // Less Reliable + //NewRelayListViewModel.Relay("wss://nostr.orangepill.dev", read = true, write = true, feedTypes = activeTypes), + //NewRelayListViewModel.Relay("wss://nostr.onsats.org", read = true, write = true, feedTypes = activeTypes), + //NewRelayListViewModel.Relay("wss://nostr.sandwich.farm", read = true, write = true, feedTypes = activeTypes), + //NewRelayListViewModel.Relay("wss://relay.nostr.ch", read = true, write = true, feedTypes = activeTypes), + //NewRelayListViewModel.Relay("wss://nostr.zebedee.cloud", read = true, write = true, feedTypes = activeTypes), + //NewRelayListViewModel.Relay("wss://nostr.rocks", read = true, write = true, feedTypes = activeTypes), + //NewRelayListViewModel.Relay("wss://nostr.fmt.wiz.biz", read = true, write = true, feedTypes = activeTypes), + + // Paid relays + NewRelayListViewModel.Relay("wss://relay.nostr.com.au", read = true, write = false, feedTypes = activeTypesGlobal), + NewRelayListViewModel.Relay("wss://eden.nostr.land", read = true, write = false, feedTypes = activeTypesGlobal), + NewRelayListViewModel.Relay("wss://nostr.milou.lol", read = true, write = false, feedTypes = activeTypesGlobal), + NewRelayListViewModel.Relay("wss://puravida.nostr.land", read = true, write = false, feedTypes = activeTypesGlobal), + NewRelayListViewModel.Relay("wss://nostr.wine", read = true, write = false, feedTypes = activeTypesGlobal), + NewRelayListViewModel.Relay("wss://nostr.inosta.cc", read = true, write = false, feedTypes = activeTypesGlobal), + NewRelayListViewModel.Relay("wss://nostr-pub.semisol.dev", read = true, write = false, feedTypes = activeTypesGlobal), + NewRelayListViewModel.Relay("wss://relay.orangepill.dev", read = true, write = false, feedTypes = activeTypesGlobal), + NewRelayListViewModel.Relay("wss://relay.nostrati.com", read = true, write = false, feedTypes = activeTypesGlobal), + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt index 36fc75847..8f81260b6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt @@ -11,10 +11,15 @@ import okhttp3.Response import okhttp3.WebSocket import okhttp3.WebSocketListener +enum class FeedType { + FOLLOWS, PUBLIC_CHATS, PRIVATE_DMS, GLOBAL +} + class Relay( - var url: String, - var read: Boolean = true, - var write: Boolean = true + var url: String, + var read: Boolean = true, + var write: Boolean = true, + var activeTypes: Set = FeedType.values().toSet(), ) { private val httpClient = OkHttpClient.Builder() .connectTimeout(100, TimeUnit.SECONDS) @@ -161,10 +166,10 @@ class Relay( if (read) { if (isConnected()) { if (isReady) { - val filters = Client.getSubscriptionFilters(requestId) + val filters = Client.getSubscriptionFilters(requestId).filter { activeTypes.intersect(it.types).isNotEmpty() } if (filters.isNotEmpty()) { val request = - """["REQ","$requestId",${filters.take(10).joinToString(",") { it.toJson() }}]""" + """["REQ","$requestId",${filters.take(10).joinToString(",") { it.filter.toJson() }}]""" //println("FILTERSSENT ${url} " + """["REQ","$requestId",${filters.joinToString(",") { it.toJson() }}]""") socket?.send(request) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt index 0466120f8..076a70424 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt @@ -1,7 +1,6 @@ package com.vitorpamplona.amethyst.service.relays import androidx.lifecycle.LiveData -import com.vitorpamplona.amethyst.service.Constants import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -27,11 +26,11 @@ object RelayPool: Relay.Listener { return relays.firstOrNull() { it.url == url } } - fun loadRelays(relayList: List? = null){ + fun loadRelays(relayList: List){ if (!relayList.isNullOrEmpty()){ relayList.forEach { addRelay(it) } } else { - Constants.defaultRelays.forEach { addRelay(it) } + Constants.convertDefaultRelays().forEach { addRelay(it) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/TypedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/TypedFilter.kt new file mode 100644 index 000000000..2612a13fa --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/TypedFilter.kt @@ -0,0 +1,8 @@ +package com.vitorpamplona.amethyst.service.relays + +import nostr.postr.JsonFilter + +class TypedFilter( + val types: Set, + val filter: JsonFilter +) \ No newline at end of file diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt index 1f735b432..24823fc46 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt @@ -33,6 +33,9 @@ import androidx.compose.material.icons.filled.Cancel import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.DownloadDone +import androidx.compose.material.icons.filled.Groups +import androidx.compose.material.icons.filled.Public +import androidx.compose.material.icons.filled.Share import androidx.compose.material.icons.filled.SyncProblem import androidx.compose.material.icons.filled.Upload import androidx.compose.material.icons.outlined.BarChart @@ -48,6 +51,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -55,18 +60,21 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.viewmodel.compose.viewModel +import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.service.relays.FeedType import java.lang.Math.round -@OptIn(ExperimentalComposeUiApi::class) + @Composable fun NewRelayListView(onClose: () -> Unit, account: Account, relayToAdd: String = "") { val postViewModel: NewRelayListViewModel = viewModel() + val ctx = LocalContext.current.applicationContext val feedState by postViewModel.relays.collectAsState() LaunchedEffect(Unit) { - postViewModel.load(account) + postViewModel.load(account, ctx) } Dialog( @@ -82,19 +90,18 @@ fun NewRelayListView(onClose: () -> Unit, account: Account, relayToAdd: String = modifier = Modifier.padding(10.dp) ) { Row( - modifier = Modifier - .fillMaxWidth(), + modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { CloseButton(onCancel = { - postViewModel.clear() + postViewModel.clear(ctx) onClose() }) PostButton( onPost = { - postViewModel.create() + postViewModel.create(ctx) onClose() }, true @@ -103,7 +110,7 @@ fun NewRelayListView(onClose: () -> Unit, account: Account, relayToAdd: String = Spacer(modifier = Modifier.height(10.dp)) - Row(modifier = Modifier.fillMaxWidth(1f), verticalAlignment = Alignment.CenterVertically) { + Row(modifier = Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) { LazyColumn( contentPadding = PaddingValues( top = 10.dp, @@ -114,15 +121,15 @@ fun NewRelayListView(onClose: () -> Unit, account: Account, relayToAdd: String = if (index == 0) ServerConfigHeader() ServerConfig(item, - onToggleDownload = { - postViewModel.toggleDownload(it) - }, - onToggleUpload = { - postViewModel.toggleUpload(it) - }, - onDelete = { - postViewModel.deleteRelay(it) - } + onToggleDownload = { postViewModel.toggleDownload(it) }, + onToggleUpload = { postViewModel.toggleUpload(it) }, + + onToggleFollows = { postViewModel.toggleFollows(it) }, + onTogglePrivateDMs = { postViewModel.toggleMessages(it) }, + onTogglePublicChats = { postViewModel.togglePublicChats(it) }, + onToggleGlobal = { postViewModel.toggleGlobal(it) }, + + onDelete = { postViewModel.deleteRelay(it) } ) } } @@ -156,7 +163,7 @@ fun ServerConfigHeader() { Column(Modifier.weight(1f)) { Row(verticalAlignment = Alignment.CenterVertically) { - Spacer(modifier = Modifier.size(20.dp)) + Spacer(modifier = Modifier.size(25.dp)) Text( text = "Posts", @@ -186,7 +193,7 @@ fun ServerConfigHeader() { color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), ) - Spacer(modifier = Modifier.size(20.dp)) + Spacer(modifier = Modifier.size(5.dp)) } } } @@ -202,9 +209,32 @@ fun ServerConfig( item: NewRelayListViewModel.Relay, onToggleDownload: (NewRelayListViewModel.Relay) -> Unit, onToggleUpload: (NewRelayListViewModel.Relay) -> Unit, + + onToggleFollows: (NewRelayListViewModel.Relay) -> Unit, + onTogglePrivateDMs: (NewRelayListViewModel.Relay) -> Unit, + onTogglePublicChats: (NewRelayListViewModel.Relay) -> Unit, + onToggleGlobal: (NewRelayListViewModel.Relay) -> Unit, + onDelete: (NewRelayListViewModel.Relay) -> Unit) { Column(Modifier.fillMaxWidth()) { - Row(verticalAlignment = Alignment.CenterVertically) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(vertical = 5.dp) + ) { + Column() { + IconButton( + modifier = Modifier.size(30.dp), + onClick = { onDelete(item) } + ) { + Icon( + imageVector = Icons.Default.Cancel, + null, + modifier = Modifier.padding(end = 5.dp).size(15.dp), + tint = Color.Red + ) + } + } + Column(Modifier.weight(1f)) { Row(verticalAlignment = Alignment.CenterVertically) { Text( @@ -214,75 +244,126 @@ fun ServerConfig( overflow = TextOverflow.Ellipsis ) } - } - Column(Modifier.weight(1f)) { Row(verticalAlignment = Alignment.CenterVertically) { - IconButton( - modifier = Modifier.size(30.dp), - onClick = { onToggleDownload(item) } - ) { - Icon( - imageVector = Icons.Default.Download, - null, - modifier = Modifier.padding(horizontal = 5.dp).size(15.dp), - tint = if (item.read) Color.Green else Color.Red - ) + Column(Modifier.weight(1f)) { + Row(verticalAlignment = Alignment.CenterVertically) { + IconButton( + modifier = Modifier.size(30.dp), + onClick = { onToggleFollows(item) } + ) { + Icon( + painterResource(R.drawable.ic_home), + "Home Feed", + modifier = Modifier.padding(end = 5.dp).size(15.dp), + tint = if (item.feedTypes.contains(FeedType.FOLLOWS)) Color.Green else MaterialTheme.colors.onSurface.copy( + alpha = 0.32f + ) + ) + } + IconButton( + modifier = Modifier.size(30.dp), + onClick = { onTogglePrivateDMs(item) } + ) { + Icon( + painterResource(R.drawable.ic_dm), + "Private Message Feed", + modifier = Modifier.padding(horizontal = 5.dp).size(15.dp), + tint = if (item.feedTypes.contains(FeedType.PRIVATE_DMS)) Color.Green else MaterialTheme.colors.onSurface.copy( + alpha = 0.32f + ) + ) + } + IconButton( + modifier = Modifier.size(30.dp), + onClick = { onTogglePublicChats(item) } + ) { + Icon( + imageVector = Icons.Default.Groups, + "Public Chat Feed", + modifier = Modifier.padding(horizontal = 5.dp).size(15.dp), + tint = if (item.feedTypes.contains(FeedType.PUBLIC_CHATS)) Color.Green else MaterialTheme.colors.onSurface.copy( + alpha = 0.32f + ) + ) + } + IconButton( + modifier = Modifier.size(30.dp), + onClick = { onToggleGlobal(item) } + ) { + Icon( + imageVector = Icons.Default.Public, + "Global Feed", + modifier = Modifier.padding(horizontal = 5.dp).size(15.dp), + tint = if (item.feedTypes.contains(FeedType.GLOBAL)) Color.Green else MaterialTheme.colors.onSurface.copy( + alpha = 0.32f + ) + ) + } + } } - Text( - text = "${countToHumanReadable(item.downloadCount)}", - maxLines = 1, - fontSize = 14.sp, - modifier = Modifier.weight(1f), - color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), - ) + Column(Modifier.weight(1f)) { + Row(verticalAlignment = Alignment.CenterVertically) { + IconButton( + modifier = Modifier.size(30.dp), + onClick = { onToggleDownload(item) } + ) { + Icon( + imageVector = Icons.Default.Download, + null, + modifier = Modifier.padding(horizontal = 5.dp).size(15.dp), + tint = if (item.read) Color.Green else MaterialTheme.colors.onSurface.copy( + alpha = 0.32f + ) + ) + } - IconButton( - modifier = Modifier.size(30.dp), - onClick = { onToggleUpload(item) } - ) { - Icon( - imageVector = Icons.Default.Upload, - null, - modifier = Modifier.padding(horizontal = 5.dp).size(15.dp), - tint = if (item.write) Color.Green else Color.Red - ) - } + Text( + text = "${countToHumanReadable(item.downloadCount)}", + maxLines = 1, + fontSize = 14.sp, + modifier = Modifier.weight(1f), + color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), + ) - Text( - text = "${countToHumanReadable(item.uploadCount)}", - maxLines = 1, - fontSize = 14.sp, - modifier = Modifier.weight(1f), - color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), - ) + IconButton( + modifier = Modifier.size(30.dp), + onClick = { onToggleUpload(item) } + ) { + Icon( + imageVector = Icons.Default.Upload, + null, + modifier = Modifier.padding(horizontal = 5.dp).size(15.dp), + tint = if (item.write) Color.Green else MaterialTheme.colors.onSurface.copy( + alpha = 0.32f + ) + ) + } - Icon( - imageVector = Icons.Default.SyncProblem, - null, - modifier = Modifier.padding(horizontal = 5.dp).size(15.dp), - tint = if (item.errorCount > 0) Color.Yellow else Color.Green - ) + Text( + text = "${countToHumanReadable(item.uploadCount)}", + maxLines = 1, + fontSize = 14.sp, + modifier = Modifier.weight(1f), + color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), + ) - Text( - text = "${countToHumanReadable(item.errorCount)}", - maxLines = 1, - fontSize = 14.sp, - modifier = Modifier.weight(1f), - color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), - ) + Icon( + imageVector = Icons.Default.SyncProblem, + null, + modifier = Modifier.padding(horizontal = 5.dp).size(15.dp), + tint = if (item.errorCount > 0) Color.Yellow else Color.Green + ) - IconButton( - modifier = Modifier.size(30.dp), - onClick = { onDelete(item) } - ) { - Icon( - imageVector = Icons.Default.Cancel, - null, - modifier = Modifier.padding(horizontal = 5.dp).size(15.dp), - tint = Color.Red - ) + Text( + text = "${countToHumanReadable(item.errorCount)}", + maxLines = 1, + fontSize = 14.sp, + modifier = Modifier.weight(1f), + color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), + ) + } } } } @@ -323,7 +404,7 @@ fun EditableServerConfig(relayToAdd: String, onNewRelay: (NewRelayListViewModel. modifier = Modifier .size(35.dp) .padding(horizontal = 5.dp), - tint = if (read) Color.Green else Color.Red + tint = if (read) Color.Green else MaterialTheme.colors.onSurface.copy(alpha = 0.32f) ) } @@ -334,7 +415,7 @@ fun EditableServerConfig(relayToAdd: String, onNewRelay: (NewRelayListViewModel. modifier = Modifier .size(35.dp) .padding(horizontal = 5.dp), - tint = if (write) Color.Green else Color.Red + tint = if (write) Color.Green else MaterialTheme.colors.onSurface.copy(alpha = 0.32f) ) } @@ -342,7 +423,7 @@ fun EditableServerConfig(relayToAdd: String, onNewRelay: (NewRelayListViewModel. onClick = { if (url.isNotBlank()) { val addedWSS = if (!url.startsWith("wss://")) "wss://$url" else url - onNewRelay(NewRelayListViewModel.Relay(addedWSS, read, write)) + onNewRelay(NewRelayListViewModel.Relay(addedWSS, read, write, feedTypes = FeedType.values().toSet())) url = "" write = true read = true @@ -351,7 +432,7 @@ fun EditableServerConfig(relayToAdd: String, onNewRelay: (NewRelayListViewModel. shape = RoundedCornerShape(20.dp), colors = ButtonDefaults .buttonColors( - backgroundColor = if (url.isNotBlank()) MaterialTheme.colors.primary else Color.Gray + backgroundColor = if (url.isNotBlank()) Color.Green else MaterialTheme.colors.onSurface.copy(alpha = 0.32f) ) ) { Text(text = "Add", color = Color.White) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListViewModel.kt index 7495b41e4..251cd045e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListViewModel.kt @@ -1,8 +1,12 @@ package com.vitorpamplona.amethyst.ui.actions +import android.content.Context +import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.ViewModel +import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.model.Account -import com.vitorpamplona.amethyst.service.Constants +import com.vitorpamplona.amethyst.service.relays.Constants +import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.RelayPool import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -18,48 +22,51 @@ class NewRelayListViewModel: ViewModel() { val write: Boolean, val errorCount: Int = 0, val downloadCount: Int = 0, - val uploadCount: Int = 0 + val uploadCount: Int = 0, + val feedTypes: Set ) private val _relays = MutableStateFlow>(emptyList()) val relays = _relays.asStateFlow() - fun load(account: Account) { + fun load(account: Account, ctx: Context) { this.account = account - clear() + clear(ctx) } - fun create() { + fun create(ctx: Context) { relays.let { - account.sendNewRelayList(it.value.associate { it.url to ContactListEvent.ReadWrite(it.read, it.write) } ) + account.saveRelayList(it.value) + LocalPreferences(ctx).saveToEncryptedStorage(account) } - clear() + clear(ctx) } - fun clear() { + fun clear(ctx: Context) { _relays.update { val relayFile = account.userProfile().relays if (relayFile != null) relayFile.map { val liveRelay = RelayPool.getRelay(it.key) + val localInfoFeedTypes = account.localRelays.filter { localRelay -> localRelay.url == it.key }.firstOrNull()?.feedTypes ?: FeedType.values().toSet() val errorCounter = liveRelay?.errorCounter ?: 0 val eventDownloadCounter = liveRelay?.eventDownloadCounter ?: 0 val eventUploadCounter = liveRelay?.eventUploadCounter ?: 0 - Relay(it.key, it.value.read, it.value.write, errorCounter, eventDownloadCounter, eventUploadCounter) + Relay(it.key, it.value.read, it.value.write, errorCounter, eventDownloadCounter, eventUploadCounter, localInfoFeedTypes) }.sortedBy { it.downloadCount }.reversed() else - Constants.defaultRelays.map { + account.localRelays.map { val liveRelay = RelayPool.getRelay(it.url) val errorCounter = liveRelay?.errorCounter ?: 0 val eventDownloadCounter = liveRelay?.eventDownloadCounter ?: 0 val eventUploadCounter = liveRelay?.eventUploadCounter ?: 0 - Relay(it.url, it.read, it.write, errorCounter, eventDownloadCounter, eventUploadCounter) + Relay(it.url, it.read, it.write, errorCounter, eventDownloadCounter, eventUploadCounter, it.feedTypes) }.sortedBy { it.downloadCount }.reversed() } } @@ -89,6 +96,38 @@ class NewRelayListViewModel: ViewModel() { it.updated(relay, relay.copy(write = !relay.write)) } } + + fun toggleFollows(relay: Relay) { + val newTypes = togglePresenceInSet(relay.feedTypes, FeedType.FOLLOWS) + _relays.update { + it.updated(relay, relay.copy(feedTypes = newTypes)) + } + } + + fun toggleMessages(relay: Relay) { + val newTypes = togglePresenceInSet(relay.feedTypes, FeedType.PRIVATE_DMS) + _relays.update { + it.updated(relay, relay.copy(feedTypes = newTypes)) + } + } + + fun togglePublicChats(relay: Relay) { + val newTypes = togglePresenceInSet(relay.feedTypes, FeedType.PUBLIC_CHATS) + _relays.update { + it.updated(relay, relay.copy(feedTypes = newTypes)) + } + } + + fun toggleGlobal(relay: Relay) { + val newTypes = togglePresenceInSet(relay.feedTypes, FeedType.GLOBAL) + _relays.update { + it.updated(relay, relay.copy( feedTypes = newTypes )) + } + } } -fun Iterable.updated(old: T, new: T): List = map { if (it == old) new else it } \ No newline at end of file +fun Iterable.updated(old: T, new: T): List = map { if (it == old) new else it } + +fun togglePresenceInSet(set: Set, item: T): Set { + return if (set.contains(item)) set.minus(item) else set.plus(item) +} \ No newline at end of file diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt index 6c1bf9d57..4d1769295 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt @@ -116,7 +116,7 @@ fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) Client.allSubscriptions().map { "${it} ${ Client.getSubscriptionFilters(it) - .joinToString { it.toJson() } + .joinToString { it.filter.toJson() } }" }.forEach { Log.d("CURRENT FILTERS", it)