- Adds support for Follow packs

- Enables top filter by pack as well.
This commit is contained in:
Vitor Pamplona
2025-04-29 17:24:03 -04:00
parent 9df0f6c368
commit 626bfc95cb
26 changed files with 440 additions and 33 deletions
@@ -29,6 +29,7 @@ import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.model.Account.Companion.APP_SPECIFIC_DATA_D_TAG
import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.service.location.LocationState import com.vitorpamplona.amethyst.service.location.LocationState
import com.vitorpamplona.amethyst.service.ots.OtsResolverBuilder import com.vitorpamplona.amethyst.service.ots.OtsResolverBuilder
@@ -140,6 +141,7 @@ import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect
import com.vitorpamplona.quartz.nip47WalletConnect.Response import com.vitorpamplona.quartz.nip47WalletConnect.Response
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
import com.vitorpamplona.quartz.nip51Lists.GeneralListEvent import com.vitorpamplona.quartz.nip51Lists.GeneralListEvent
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
@@ -194,6 +196,7 @@ import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.toSet
import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -697,9 +700,11 @@ class Account(
LiveFollowList(authorsPlusMe = setOf(signer.pubKey)) LiveFollowList(authorsPlusMe = setOf(signer.pubKey))
} }
} else { } else {
val peopleList = noteState.note.event as? GeneralListEvent val noteEvent = noteState.note.event
if (peopleList != null) { if (noteEvent is GeneralListEvent) {
waitToDecrypt(peopleList) ?: LiveFollowList(authorsPlusMe = setOf(signer.pubKey)) waitToDecrypt(noteEvent) ?: LiveFollowList(authorsPlusMe = setOf(signer.pubKey))
} else if (noteEvent is FollowListEvent) {
LiveFollowList(authors = noteEvent.pubKeys().toSet(), authorsPlusMe = setOf(signer.pubKey) + noteEvent.pubKeys())
} else { } else {
LiveFollowList(authorsPlusMe = setOf(signer.pubKey)) LiveFollowList(authorsPlusMe = setOf(signer.pubKey))
} }
@@ -3622,10 +3627,15 @@ class Account(
fun getAllPeopleLists(pubkey: HexKey): List<AddressableNote> = fun getAllPeopleLists(pubkey: HexKey): List<AddressableNote> =
LocalCache.addressables LocalCache.addressables
.filter { _, addressableNote -> .filter { _, addressableNote ->
val event = (addressableNote.event as? PeopleListEvent) val noteEvent = addressableNote.event
event != null &&
event.pubKey == pubkey && if (noteEvent is PeopleListEvent) {
(event.hasAnyTaggedUser() || event.cachedPrivateTags()?.isNotEmpty() == true) noteEvent.pubKey == pubkey && (noteEvent.hasAnyTaggedUser() || noteEvent.cachedPrivateTags()?.isNotEmpty() == true)
} else if (noteEvent is FollowListEvent) {
noteEvent.pubKey == pubkey && noteEvent.hasAnyTaggedUser()
} else {
false
}
} }
fun markAsRead( fun markAsRead(
@@ -109,6 +109,7 @@ import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentRequestEvent
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip51Lists.PinListEvent import com.vitorpamplona.quartz.nip51Lists.PinListEvent
@@ -828,6 +829,13 @@ object LocalCache : ILocalCache {
consumeBaseReplaceable(event, relay) consumeBaseReplaceable(event, relay)
} }
fun consume(
event: FollowListEvent,
relay: Relay?,
) {
consumeBaseReplaceable(event, relay)
}
private fun consume( private fun consume(
event: AdvertisedRelayListEvent, event: AdvertisedRelayListEvent,
relay: Relay?, relay: Relay?,
@@ -2622,6 +2630,7 @@ object LocalCache : ILocalCache {
is FileServersEvent -> consume(event, relay) is FileServersEvent -> consume(event, relay)
is FileStorageEvent -> consume(event, relay) is FileStorageEvent -> consume(event, relay)
is FileStorageHeaderEvent -> consume(event, relay) is FileStorageHeaderEvent -> consume(event, relay)
is FollowListEvent -> consume(event, relay)
is GiftWrapEvent -> consume(event, relay) is GiftWrapEvent -> consume(event, relay)
is GitIssueEvent -> consume(event, relay) is GitIssueEvent -> consume(event, relay)
is GitReplyEvent -> consume(event, relay) is GitReplyEvent -> consume(event, relay)
@@ -53,6 +53,7 @@ import com.vitorpamplona.quartz.nip38UserStatus.StatusEvent
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip52Calendar.CalendarDateSlotEvent import com.vitorpamplona.quartz.nip52Calendar.CalendarDateSlotEvent
@@ -135,6 +136,7 @@ class AccountFilterAssembler(
kinds = kinds =
listOf( listOf(
PeopleListEvent.KIND, PeopleListEvent.KIND,
FollowListEvent.KIND,
MuteListEvent.KIND, MuteListEvent.KIND,
BadgeProfilesEvent.KIND, BadgeProfilesEvent.KIND,
EmojiPackSelectionEvent.KIND, EmojiPackSelectionEvent.KIND,
@@ -55,6 +55,7 @@ import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.pack.EmojiPackEvent import com.vitorpamplona.quartz.nip30CustomEmoji.pack.EmojiPackEvent
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip51Lists.PinListEvent import com.vitorpamplona.quartz.nip51Lists.PinListEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
@@ -226,6 +227,7 @@ class SearchFilterAssembler(
listOf( listOf(
InteractiveStoryPrologueEvent.KIND, InteractiveStoryPrologueEvent.KIND,
InteractiveStorySceneEvent.KIND, InteractiveStorySceneEvent.KIND,
FollowListEvent.KIND,
), ),
search = mySearchString, search = mySearchString,
limit = 100, limit = 100,
@@ -70,6 +70,7 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@@ -252,7 +253,15 @@ fun RenderOption(option: Name) {
) { ) {
val noteState by observeNote(option.note) val noteState by observeNote(option.note)
val name = (noteState?.note?.event as? PeopleListEvent)?.nameOrTitle() ?: option.note.dTag() ?: "" val noteEvent = noteState.note.event
val name =
if (noteEvent is PeopleListEvent) {
noteEvent.nameOrTitle() ?: option.note.dTag()
} else if (noteEvent is FollowListEvent) {
noteEvent.nameOrTitle() ?: option.note.dTag()
} else {
option.note.dTag()
}
Text(text = name, color = MaterialTheme.colorScheme.onSurface) Text(text = name, color = MaterialTheme.colorScheme.onSurface)
} }
@@ -80,6 +80,7 @@ import com.vitorpamplona.amethyst.ui.note.elements.ShowForkInformation
import com.vitorpamplona.amethyst.ui.note.elements.TimeAgo import com.vitorpamplona.amethyst.ui.note.elements.TimeAgo
import com.vitorpamplona.amethyst.ui.note.types.BadgeDisplay import com.vitorpamplona.amethyst.ui.note.types.BadgeDisplay
import com.vitorpamplona.amethyst.ui.note.types.DisplayDMRelayList import com.vitorpamplona.amethyst.ui.note.types.DisplayDMRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayFollowList
import com.vitorpamplona.amethyst.ui.note.types.DisplayNIP65RelayList import com.vitorpamplona.amethyst.ui.note.types.DisplayNIP65RelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayPeopleList import com.vitorpamplona.amethyst.ui.note.types.DisplayPeopleList
import com.vitorpamplona.amethyst.ui.note.types.DisplayRelaySet import com.vitorpamplona.amethyst.ui.note.types.DisplayRelaySet
@@ -181,6 +182,7 @@ import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip51Lists.PinListEvent import com.vitorpamplona.quartz.nip51Lists.PinListEvent
import com.vitorpamplona.quartz.nip51Lists.RelaySetEvent import com.vitorpamplona.quartz.nip51Lists.RelaySetEvent
@@ -624,6 +626,7 @@ private fun RenderNoteRow(
is BadgeAwardEvent -> RenderBadgeAward(baseNote, backgroundColor, accountViewModel, nav) is BadgeAwardEvent -> RenderBadgeAward(baseNote, backgroundColor, accountViewModel, nav)
is FhirResourceEvent -> RenderFhirResource(baseNote, accountViewModel, nav) is FhirResourceEvent -> RenderFhirResource(baseNote, accountViewModel, nav)
is PeopleListEvent -> DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav) is PeopleListEvent -> DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
is FollowListEvent -> DisplayFollowList(baseNote, backgroundColor, accountViewModel, nav)
is RelaySetEvent -> DisplayRelaySet(baseNote, backgroundColor, accountViewModel, nav) is RelaySetEvent -> DisplayRelaySet(baseNote, backgroundColor, accountViewModel, nav)
is ChatMessageRelayListEvent -> DisplayDMRelayList(baseNote, backgroundColor, accountViewModel, nav) is ChatMessageRelayListEvent -> DisplayDMRelayList(baseNote, backgroundColor, accountViewModel, nav)
is AdvertisedRelayListEvent -> DisplayNIP65RelayList(baseNote, backgroundColor, accountViewModel, nav) is AdvertisedRelayListEvent -> DisplayNIP65RelayList(baseNote, backgroundColor, accountViewModel, nav)
@@ -0,0 +1,134 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.note.types
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.components.ShowMoreButton
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.note.UserCompose
import com.vitorpamplona.amethyst.ui.note.getGradient
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUserIds
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun DisplayFollowList(
baseNote: Note,
backgroundColor: MutableState<Color>,
accountViewModel: AccountViewModel,
nav: INav,
) {
val noteEvent = baseNote.event as? FollowListEvent ?: return
var members by remember { mutableStateOf<ImmutableList<User>>(persistentListOf()) }
var expanded by remember { mutableStateOf(false) }
val toMembersShow =
if (expanded) {
members
} else {
members.take(3)
}
val name by remember { derivedStateOf { "#${noteEvent.nameOrTitle() ?: noteEvent.dTag()}" } }
Text(
text = name,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier =
Modifier
.fillMaxWidth()
.padding(5.dp),
textAlign = TextAlign.Center,
)
LaunchedEffect(Unit) {
accountViewModel.loadUsers(noteEvent.taggedUserIds()) {
members = it
}
}
Box {
FlowRow(modifier = Modifier.padding(top = 5.dp)) {
toMembersShow.forEach { user ->
Column(modifier = Modifier.fillMaxWidth()) {
UserCompose(
user,
accountViewModel = accountViewModel,
nav = nav,
)
HorizontalDivider(
thickness = DividerThickness,
)
}
}
}
if (members.size > 3 && !expanded) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier =
Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.background(getGradient(backgroundColor)),
) {
ShowMoreButton { expanded = !expanded }
}
}
}
}
@@ -45,6 +45,7 @@ import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip51Lists.PinListEvent import com.vitorpamplona.quartz.nip51Lists.PinListEvent
@@ -135,6 +136,7 @@ class FollowListState(
( (
( (
noteEvent is PeopleListEvent || noteEvent is PeopleListEvent ||
noteEvent is FollowListEvent ||
noteEvent is MuteListEvent || noteEvent is MuteListEvent ||
noteEvent is ContactListEvent noteEvent is ContactListEvent
) || ) ||
@@ -288,13 +290,22 @@ class ResourceName(
class PeopleListName( class PeopleListName(
val note: AddressableNote, val note: AddressableNote,
) : Name() { ) : Name() {
override fun name() = (note.event as? PeopleListEvent)?.nameOrTitle() ?: note.dTag() ?: "" override fun name(): String {
val noteEvent = note.event
return if (noteEvent is PeopleListEvent) {
noteEvent.nameOrTitle() ?: note.dTag()
} else if (noteEvent is FollowListEvent) {
noteEvent.nameOrTitle() ?: note.dTag()
} else {
note.dTag()
}
}
} }
class CommunityName( class CommunityName(
val note: AddressableNote, val note: AddressableNote,
) : Name() { ) : Name() {
override fun name() = "/n/${(note.dTag() ?: "")}" override fun name() = "/n/${(note.dTag())}"
} }
@Immutable @Immutable
@@ -67,7 +67,7 @@ open class DiscoverChatFeedFilter(
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection) override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
fun buildFilterParams(account: Account): FilterByListParams = fun buildFilterParams(account: Account): FilterByListParams =
FilterByListParams.Companion.create( FilterByListParams.create(
userHex = account.userProfile().pubkeyHex, userHex = account.userProfile().pubkeyHex,
selectedListName = account.settings.defaultDiscoveryFollowList.value, selectedListName = account.settings.defaultDiscoveryFollowList.value,
followLists = account.liveDiscoveryFollowLists.value, followLists = account.liveDiscoveryFollowLists.value,
@@ -44,7 +44,7 @@ open class DiscoverCommunityFeedFilter(
override fun feed(): List<Note> { override fun feed(): List<Note> {
val filterParams = val filterParams =
FilterByListParams.Companion.create( FilterByListParams.create(
userHex = account.userProfile().pubkeyHex, userHex = account.userProfile().pubkeyHex,
selectedListName = account.settings.defaultDiscoveryFollowList.value, selectedListName = account.settings.defaultDiscoveryFollowList.value,
followLists = account.liveDiscoveryFollowLists.value, followLists = account.liveDiscoveryFollowLists.value,
@@ -73,7 +73,7 @@ open class DiscoverCommunityFeedFilter(
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> { protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
// here, we need to look for CommunityDefinition in new collection AND new CommunityDefinition from Post Approvals // here, we need to look for CommunityDefinition in new collection AND new CommunityDefinition from Post Approvals
val filterParams = val filterParams =
FilterByListParams.Companion.create( FilterByListParams.create(
userHex = account.userProfile().pubkeyHex, userHex = account.userProfile().pubkeyHex,
selectedListName = account.settings.defaultDiscoveryFollowList.value, selectedListName = account.settings.defaultDiscoveryFollowList.value,
followLists = account.liveDiscoveryFollowLists.value, followLists = account.liveDiscoveryFollowLists.value,
@@ -55,7 +55,7 @@ open class DiscoverLiveFeedFilter(
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> { protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val filterParams = val filterParams =
FilterByListParams.Companion.create( FilterByListParams.create(
userHex = account.userProfile().pubkeyHex, userHex = account.userProfile().pubkeyHex,
selectedListName = account.settings.defaultDiscoveryFollowList.value, selectedListName = account.settings.defaultDiscoveryFollowList.value,
followLists = account.liveDiscoveryFollowLists.value, followLists = account.liveDiscoveryFollowLists.value,
@@ -55,7 +55,7 @@ open class DiscoverMarketplaceFeedFilter(
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection) override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
fun buildFilterParams(account: Account): FilterByListParams = fun buildFilterParams(account: Account): FilterByListParams =
FilterByListParams.Companion.create( FilterByListParams.create(
account.userProfile().pubkeyHex, account.userProfile().pubkeyHex,
account.settings.defaultDiscoveryFollowList.value, account.settings.defaultDiscoveryFollowList.value,
account.liveDiscoveryFollowLists.value, account.liveDiscoveryFollowLists.value,
@@ -56,7 +56,7 @@ open class DiscoverNIP89FeedFilter(
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection) override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
fun buildFilterParams(account: Account): FilterByListParams = fun buildFilterParams(account: Account): FilterByListParams =
FilterByListParams.Companion.create( FilterByListParams.create(
account.userProfile().pubkeyHex, account.userProfile().pubkeyHex,
account.settings.defaultDiscoveryFollowList.value, account.settings.defaultDiscoveryFollowList.value,
account.liveDiscoveryFollowLists.value, account.liveDiscoveryFollowLists.value,
@@ -43,8 +43,8 @@ open class NIP90ContentDiscoveryResponseFilter(
open fun followList(): String = account.settings.defaultDiscoveryFollowList.value open fun followList(): String = account.settings.defaultDiscoveryFollowList.value
override fun showHiddenKey(): Boolean = override fun showHiddenKey(): Boolean =
followList() == PeopleListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) || followList() == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
followList() == MuteListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) followList() == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
fun acceptableEvent(note: Note): Boolean { fun acceptableEvent(note: Note): Boolean {
val noteEvent = note.event val noteEvent = note.event
@@ -72,7 +72,7 @@ open class NIP90ContentDiscoveryResponseFilter(
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection) override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
fun buildFilterParams(account: Account): FilterByListParams = fun buildFilterParams(account: Account): FilterByListParams =
FilterByListParams.Companion.create( FilterByListParams.create(
account.userProfile().pubkeyHex, account.userProfile().pubkeyHex,
account.settings.defaultDiscoveryFollowList.value, account.settings.defaultDiscoveryFollowList.value,
account.liveDiscoveryFollowLists.value, account.liveDiscoveryFollowLists.value,
@@ -57,7 +57,7 @@ class HomeConversationsFeedFilter(
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection) override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
fun buildFilterParams(account: Account): FilterByListParams = fun buildFilterParams(account: Account): FilterByListParams =
FilterByListParams.Companion.create( FilterByListParams.create(
userHex = account.userProfile().pubkeyHex, userHex = account.userProfile().pubkeyHex,
selectedListName = account.settings.defaultHomeFollowList.value, selectedListName = account.settings.defaultHomeFollowList.value,
followLists = account.liveHomeFollowLists.value, followLists = account.liveHomeFollowLists.value,
@@ -51,7 +51,7 @@ class HomeNewThreadFeedFilter(
account.settings.defaultHomeFollowList.value == MuteListEvent.blockListFor(account.userProfile().pubkeyHex) account.settings.defaultHomeFollowList.value == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
fun buildFilterParams(account: Account): FilterByListParams = fun buildFilterParams(account: Account): FilterByListParams =
FilterByListParams.Companion.create( FilterByListParams.create(
userHex = account.userProfile().pubkeyHex, userHex = account.userProfile().pubkeyHex,
selectedListName = account.settings.defaultHomeFollowList.value, selectedListName = account.settings.defaultHomeFollowList.value,
followLists = account.liveHomeFollowLists.value, followLists = account.liveHomeFollowLists.value,
@@ -57,12 +57,12 @@ class NotificationFeedFilter(
override fun showHiddenKey(): Boolean = override fun showHiddenKey(): Boolean =
account.settings.defaultNotificationFollowList.value == account.settings.defaultNotificationFollowList.value ==
PeopleListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) || PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
account.settings.defaultNotificationFollowList.value == account.settings.defaultNotificationFollowList.value ==
MuteListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
fun buildFilterParams(account: Account): FilterByListParams = fun buildFilterParams(account: Account): FilterByListParams =
FilterByListParams.Companion.create( FilterByListParams.create(
userHex = account.userProfile().pubkeyHex, userHex = account.userProfile().pubkeyHex,
selectedListName = account.settings.defaultNotificationFollowList.value, selectedListName = account.settings.defaultNotificationFollowList.value,
followLists = account.liveNotificationFollowLists.value, followLists = account.liveNotificationFollowLists.value,
@@ -41,6 +41,7 @@ import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip51Lists.PinListEvent import com.vitorpamplona.quartz.nip51Lists.PinListEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
@@ -179,6 +180,7 @@ class UserProfileFilterAssembler(
listOf( listOf(
BookmarkListEvent.KIND, BookmarkListEvent.KIND,
PeopleListEvent.KIND, PeopleListEvent.KIND,
FollowListEvent.KIND,
AppRecommendationEvent.KIND, AppRecommendationEvent.KIND,
), ),
authors = listOf(state.user.pubkeyHex), authors = listOf(state.user.pubkeyHex),
@@ -40,8 +40,8 @@ class UserProfileGalleryFeedFilter(
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + "ProfileGallery" override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + "ProfileGallery"
override fun showHiddenKey(): Boolean = override fun showHiddenKey(): Boolean =
account.settings.defaultStoriesFollowList.value == PeopleListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) || account.settings.defaultStoriesFollowList.value == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
account.settings.defaultStoriesFollowList.value == MuteListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) account.settings.defaultStoriesFollowList.value == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
override fun feed(): List<Note> { override fun feed(): List<Note> {
val params = buildFilterParams(account) val params = buildFilterParams(account)
@@ -77,7 +77,7 @@ class UserProfileGalleryFeedFilter(
} }
fun buildFilterParams(account: Account): FilterByListParams = fun buildFilterParams(account: Account): FilterByListParams =
FilterByListParams.Companion.create( FilterByListParams.create(
userHex = account.userProfile().pubkeyHex, userHex = account.userProfile().pubkeyHex,
selectedListName = account.settings.defaultStoriesFollowList.value, selectedListName = account.settings.defaultStoriesFollowList.value,
followLists = account.liveStoriesFollowLists.value, followLists = account.liveStoriesFollowLists.value,
@@ -118,6 +118,7 @@ import com.vitorpamplona.amethyst.ui.note.types.AudioHeader
import com.vitorpamplona.amethyst.ui.note.types.AudioTrackHeader import com.vitorpamplona.amethyst.ui.note.types.AudioTrackHeader
import com.vitorpamplona.amethyst.ui.note.types.BadgeDisplay import com.vitorpamplona.amethyst.ui.note.types.BadgeDisplay
import com.vitorpamplona.amethyst.ui.note.types.DisplayDMRelayList import com.vitorpamplona.amethyst.ui.note.types.DisplayDMRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayFollowList
import com.vitorpamplona.amethyst.ui.note.types.DisplayNIP65RelayList import com.vitorpamplona.amethyst.ui.note.types.DisplayNIP65RelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayPeopleList import com.vitorpamplona.amethyst.ui.note.types.DisplayPeopleList
import com.vitorpamplona.amethyst.ui.note.types.DisplayRelaySet import com.vitorpamplona.amethyst.ui.note.types.DisplayRelaySet
@@ -196,6 +197,7 @@ import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip51Lists.PinListEvent import com.vitorpamplona.quartz.nip51Lists.PinListEvent
import com.vitorpamplona.quartz.nip51Lists.RelaySetEvent import com.vitorpamplona.quartz.nip51Lists.RelaySetEvent
@@ -531,6 +533,8 @@ private fun FullBleedNoteCompose(
FileStorageHeaderDisplay(baseNote, roundedCorner = true, ContentScale.FillWidth, accountViewModel = accountViewModel) FileStorageHeaderDisplay(baseNote, roundedCorner = true, ContentScale.FillWidth, accountViewModel = accountViewModel)
} else if (noteEvent is PeopleListEvent) { } else if (noteEvent is PeopleListEvent) {
DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav) DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
} else if (noteEvent is FollowListEvent) {
DisplayFollowList(baseNote, backgroundColor, accountViewModel, nav)
} else if (noteEvent is AudioTrackEvent) { } else if (noteEvent is AudioTrackEvent) {
AudioTrackHeader(noteEvent, baseNote, ContentScale.FillWidth, accountViewModel, nav) AudioTrackHeader(noteEvent, baseNote, ContentScale.FillWidth, accountViewModel, nav)
} else if (noteEvent is AudioHeaderEvent) { } else if (noteEvent is AudioHeaderEvent) {
@@ -45,8 +45,8 @@ class VideoFeedFilter(
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + account.settings.defaultStoriesFollowList.value override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + account.settings.defaultStoriesFollowList.value
override fun showHiddenKey(): Boolean = override fun showHiddenKey(): Boolean =
account.settings.defaultStoriesFollowList.value == PeopleListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) || account.settings.defaultStoriesFollowList.value == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
account.settings.defaultStoriesFollowList.value == MuteListEvent.Companion.blockListFor(account.userProfile().pubkeyHex) account.settings.defaultStoriesFollowList.value == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
override fun feed(): List<Note> { override fun feed(): List<Note> {
val params = buildFilterParams(account) val params = buildFilterParams(account)
@@ -120,7 +120,7 @@ class VideoFeedFilter(
} }
fun buildFilterParams(account: Account): FilterByListParams = fun buildFilterParams(account: Account): FilterByListParams =
FilterByListParams.Companion.create( FilterByListParams.create(
userHex = account.userProfile().pubkeyHex, userHex = account.userProfile().pubkeyHex,
selectedListName = account.settings.defaultStoriesFollowList.value, selectedListName = account.settings.defaultStoriesFollowList.value,
followLists = account.liveStoriesFollowLists.value, followLists = account.liveStoriesFollowLists.value,
@@ -70,10 +70,12 @@ import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
import com.vitorpamplona.quartz.nip38UserStatus.StatusEvent import com.vitorpamplona.quartz.nip38UserStatus.StatusEvent
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
import com.vitorpamplona.quartz.nip46RemoteSigner.NostrConnectEvent
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentRequestEvent import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentRequestEvent
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.FollowListEvent
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip51Lists.PinListEvent import com.vitorpamplona.quartz.nip51Lists.PinListEvent
@@ -197,6 +199,7 @@ class EventFactory {
FileStorageEvent.KIND -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig) FileStorageEvent.KIND -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig)
FileStorageHeaderEvent.KIND -> FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig) FileStorageHeaderEvent.KIND -> FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig)
FhirResourceEvent.KIND -> FhirResourceEvent(id, pubKey, createdAt, tags, content, sig) FhirResourceEvent.KIND -> FhirResourceEvent(id, pubKey, createdAt, tags, content, sig)
FollowListEvent.KIND -> FollowListEvent(id, pubKey, createdAt, tags, content, sig)
GenericRepostEvent.KIND -> GenericRepostEvent(id, pubKey, createdAt, tags, content, sig) GenericRepostEvent.KIND -> GenericRepostEvent(id, pubKey, createdAt, tags, content, sig)
GiftWrapEvent.KIND -> GiftWrapEvent(id, pubKey, createdAt, tags, content, sig) GiftWrapEvent.KIND -> GiftWrapEvent(id, pubKey, createdAt, tags, content, sig)
GitIssueEvent.KIND -> GitIssueEvent(id, pubKey, createdAt, tags, content, sig) GitIssueEvent.KIND -> GitIssueEvent(id, pubKey, createdAt, tags, content, sig)
@@ -220,9 +223,7 @@ class EventFactory {
MetadataEvent.KIND -> MetadataEvent(id, pubKey, createdAt, tags, content, sig) MetadataEvent.KIND -> MetadataEvent(id, pubKey, createdAt, tags, content, sig)
MuteListEvent.KIND -> MuteListEvent(id, pubKey, createdAt, tags, content, sig) MuteListEvent.KIND -> MuteListEvent(id, pubKey, createdAt, tags, content, sig)
NNSEvent.KIND -> NNSEvent(id, pubKey, createdAt, tags, content, sig) NNSEvent.KIND -> NNSEvent(id, pubKey, createdAt, tags, content, sig)
com.vitorpamplona.quartz.nip46RemoteSigner.NostrConnectEvent.KIND -> NostrConnectEvent.KIND -> NostrConnectEvent(id, pubKey, createdAt, tags, content, sig)
com.vitorpamplona.quartz.nip46RemoteSigner
.NostrConnectEvent(id, pubKey, createdAt, tags, content, sig)
NIP90StatusEvent.KIND -> NIP90StatusEvent(id, pubKey, createdAt, tags, content, sig) NIP90StatusEvent.KIND -> NIP90StatusEvent(id, pubKey, createdAt, tags, content, sig)
NIP90ContentDiscoveryRequestEvent.KIND -> NIP90ContentDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig) NIP90ContentDiscoveryRequestEvent.KIND -> NIP90ContentDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig)
NIP90ContentDiscoveryResponseEvent.KIND -> NIP90ContentDiscoveryResponseEvent(id, pubKey, createdAt, tags, content, sig) NIP90ContentDiscoveryResponseEvent.KIND -> NIP90ContentDiscoveryResponseEvent(id, pubKey, createdAt, tags, content, sig)
@@ -0,0 +1,142 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip51Lists
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip31Alts.AltTag
import com.vitorpamplona.quartz.nip51Lists.tags.DescriptionTag
import com.vitorpamplona.quartz.nip51Lists.tags.ImageTag
import com.vitorpamplona.quartz.nip51Lists.tags.ImageTag.Companion.parse
import com.vitorpamplona.quartz.nip51Lists.tags.NameTag
import com.vitorpamplona.quartz.nip51Lists.tags.TitleTag
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
class FollowListEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
fun pubKeys() = tags.mapNotNull(PTag::parseKey)
fun pubKeyHints() = tags.mapNotNull(PTag::parseAsHint)
fun name() = tags.firstNotNullOfOrNull(NameTag::parse)
fun title() = tags.firstNotNullOfOrNull(TitleTag::parse)
fun description() = tags.firstNotNullOfOrNull(DescriptionTag::parse)
fun nameOrTitle() = name() ?: title()
fun image() = tags.firstNotNullOfOrNull(ImageTag::parse)
companion object {
const val KIND = 39089
const val ALT = "List of people to follow"
fun createListWithUser(
name: String,
pubKeyHex: String,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (FollowListEvent) -> Unit,
) {
create(
content = "",
tags = arrayOf(arrayOf("d", name), arrayOf("p", pubKeyHex)),
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
fun addUsers(
earlierVersion: FollowListEvent,
listPubKeyHex: List<String>,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (FollowListEvent) -> Unit,
) {
create(
content = earlierVersion.content,
tags =
earlierVersion.tags.plus(
listPubKeyHex.map { arrayOf("p", it) },
),
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
fun addUser(
earlierVersion: FollowListEvent,
pubKeyHex: String,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (FollowListEvent) -> Unit,
) = addUsers(earlierVersion, listOf(pubKeyHex), signer, createdAt, onReady)
fun removeUser(
earlierVersion: FollowListEvent,
pubKeyHex: String,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (FollowListEvent) -> Unit,
) {
create(
content = earlierVersion.content,
tags =
earlierVersion.tags
.filter { it.size > 1 && !(it[0] == "p" && it[1] == pubKeyHex) }
.toTypedArray(),
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
fun create(
content: String,
tags: Array<Array<String>>,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (FollowListEvent) -> Unit,
) {
val newTags =
if (tags.any { it.size > 1 && it[0] == "alt" }) {
tags
} else {
tags + AltTag.assemble(ALT)
}
signer.sign(createdAt, KIND, newTags, content, onReady)
}
}
}
@@ -34,6 +34,8 @@ import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.geohash.geohashes import com.vitorpamplona.quartz.nip01Core.tags.geohash.geohashes
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.HashtagTag import com.vitorpamplona.quartz.nip01Core.tags.hashtags.HashtagTag
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip51Lists.tags.DescriptionTag
import com.vitorpamplona.quartz.nip51Lists.tags.ImageTag
import com.vitorpamplona.quartz.nip51Lists.tags.NameTag import com.vitorpamplona.quartz.nip51Lists.tags.NameTag
import com.vitorpamplona.quartz.nip51Lists.tags.TitleTag import com.vitorpamplona.quartz.nip51Lists.tags.TitleTag
import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.ImmutableSet
@@ -63,6 +65,10 @@ abstract class GeneralListEvent(
@Deprecated("NIP-51 has deprecated Title. Use name instead", ReplaceWith("name()")) @Deprecated("NIP-51 has deprecated Title. Use name instead", ReplaceWith("name()"))
fun title() = tags.firstNotNullOfOrNull(TitleTag::parse) fun title() = tags.firstNotNullOfOrNull(TitleTag::parse)
fun description() = tags.firstNotNullOfOrNull(DescriptionTag::parse)
fun image() = tags.firstNotNullOfOrNull(ImageTag::parse)
fun nameOrTitle() = name() ?: title() fun nameOrTitle() = name() ?: title()
fun filterTagList( fun filterTagList(
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip51Lists.tags
class DescriptionTag {
companion object {
const val TAG_NAME = "description"
@JvmStatic
fun parse(tag: Array<String>): String? {
if (tag.size < 2 || tag[0] != TAG_NAME || tag[1].isEmpty()) return null
return tag[1]
}
@JvmStatic
fun assemble(name: String) = arrayOf(TAG_NAME, name)
}
}
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip51Lists.tags
class ImageTag {
companion object {
const val TAG_NAME = "image"
@JvmStatic
fun parse(tag: Array<String>): String? {
if (tag.size < 2 || tag[0] != TAG_NAME || tag[1].isEmpty()) return null
return tag[1]
}
@JvmStatic
fun assemble(name: String) = arrayOf(TAG_NAME, name)
}
}