mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-10 23:36:42 +01:00
Add other functions and adapt them to the code already present.
This commit is contained in:
@@ -90,6 +90,7 @@ import com.vitorpamplona.amethyst.model.torState.TorRelayState
|
|||||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||||
import com.vitorpamplona.amethyst.service.ots.OkHttpOtsResolverBuilder
|
import com.vitorpamplona.amethyst.service.ots.OkHttpOtsResolverBuilder
|
||||||
import com.vitorpamplona.amethyst.service.uploads.FileHeader
|
import com.vitorpamplona.amethyst.service.uploads.FileHeader
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.FollowSet
|
||||||
import com.vitorpamplona.quartz.experimental.bounties.BountyAddValueEvent
|
import com.vitorpamplona.quartz.experimental.bounties.BountyAddValueEvent
|
||||||
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
||||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryBaseEvent
|
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryBaseEvent
|
||||||
@@ -215,6 +216,7 @@ import kotlinx.coroutines.flow.StateFlow
|
|||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@@ -839,6 +841,23 @@ class Account(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getFollowSetNotes() =
|
||||||
|
withContext(Dispatchers.Default) {
|
||||||
|
val followSetNotes = LocalCache.getFollowSetNotesFor(userProfile())
|
||||||
|
userProfile().updateFollowSetNotes(followSetNotes)
|
||||||
|
// userProfile().followSets = followSetNotes
|
||||||
|
println("Number of follow sets: ${followSetNotes.size}")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun mapNoteToFollowSet(note: Note): FollowSet =
|
||||||
|
FollowSet
|
||||||
|
.mapEventToSet(
|
||||||
|
event = note.event as PeopleListEvent,
|
||||||
|
signer,
|
||||||
|
)
|
||||||
|
|
||||||
|
// fun followSetNotesFlow() = MutableStateFlow(userProfile().followSets)
|
||||||
|
|
||||||
suspend fun updateAttestations() = sendAutomatic(otsState.updateAttestations())
|
suspend fun updateAttestations() = sendAutomatic(otsState.updateAttestations())
|
||||||
|
|
||||||
suspend fun follow(user: User) = sendMyPublicAndPrivateOutbox(kind3FollowList.follow(user))
|
suspend fun follow(user: User) = sendMyPublicAndPrivateOutbox(kind3FollowList.follow(user))
|
||||||
@@ -1609,60 +1628,6 @@ class Account(
|
|||||||
|
|
||||||
suspend fun hideUser(pubkeyHex: HexKey) {
|
suspend fun hideUser(pubkeyHex: HexKey) {
|
||||||
sendMyPublicAndPrivateOutbox(muteList.hideUser(pubkeyHex))
|
sendMyPublicAndPrivateOutbox(muteList.hideUser(pubkeyHex))
|
||||||
fun getAppSpecificDataNote() = LocalCache.getOrCreateAddressableNote(AppSpecificDataEvent.createAddress(userProfile().pubkeyHex, APP_SPECIFIC_DATA_D_TAG))
|
|
||||||
|
|
||||||
fun getAppSpecificDataFlow(): StateFlow<NoteState> = getAppSpecificDataNote().flow().metadata.stateFlow
|
|
||||||
|
|
||||||
fun getBlockListNote() = LocalCache.getOrCreateAddressableNote(PeopleListEvent.createBlockAddress(userProfile().pubkeyHex))
|
|
||||||
|
|
||||||
fun getMuteListNote() = LocalCache.getOrCreateAddressableNote(MuteListEvent.createAddress(userProfile().pubkeyHex))
|
|
||||||
|
|
||||||
suspend fun getFollowSetNotes() =
|
|
||||||
withContext(Dispatchers.Default) {
|
|
||||||
val followSetNotes = LocalCache.getFollowSetNotesFor(userProfile())
|
|
||||||
userProfile().updateFollowSetNotes(followSetNotes)
|
|
||||||
// userProfile().followSets = followSetNotes
|
|
||||||
println("Number of follow sets: ${followSetNotes.size}")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun mapNoteToFollowSet(note: Note): FollowSet =
|
|
||||||
FollowSet
|
|
||||||
.mapEventToSet(
|
|
||||||
event = note.event as PeopleListEvent,
|
|
||||||
signer,
|
|
||||||
)
|
|
||||||
|
|
||||||
// fun followSetNotesFlow() = MutableStateFlow(userProfile().followSets)
|
|
||||||
|
|
||||||
fun getMuteListFlow(): StateFlow<NoteState> = getMuteListNote().flow().metadata.stateFlow
|
|
||||||
|
|
||||||
fun getBlockList(): PeopleListEvent? = getBlockListNote().event as? PeopleListEvent
|
|
||||||
|
|
||||||
fun getMuteList(): MuteListEvent? = getMuteListNote().event as? MuteListEvent
|
|
||||||
|
|
||||||
fun hideWord(word: String) {
|
|
||||||
val muteList = getMuteList()
|
|
||||||
|
|
||||||
if (muteList != null) {
|
|
||||||
MuteListEvent.addWord(
|
|
||||||
earlierVersion = muteList,
|
|
||||||
word = word,
|
|
||||||
isPrivate = true,
|
|
||||||
signer = signer,
|
|
||||||
) {
|
|
||||||
Amethyst.instance.client.send(it)
|
|
||||||
LocalCache.consume(it, null)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MuteListEvent.createListWithWord(
|
|
||||||
word = word,
|
|
||||||
isPrivate = true,
|
|
||||||
signer = signer,
|
|
||||||
) {
|
|
||||||
Amethyst.instance.client.send(it)
|
|
||||||
LocalCache.consume(it, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun showUser(pubkeyHex: HexKey) {
|
suspend fun showUser(pubkeyHex: HexKey) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2024 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* 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
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
@@ -32,9 +32,10 @@ class FollowSetFeedFilter(
|
|||||||
override fun feedKey(): String = account.userProfile().pubkeyHex
|
override fun feedKey(): String = account.userProfile().pubkeyHex
|
||||||
|
|
||||||
override fun feed(): List<FollowSet> {
|
override fun feed(): List<FollowSet> {
|
||||||
|
val followSetCache = mutableListOf<FollowSet>()
|
||||||
|
account.scope.launch {
|
||||||
val userFollowSets = account.userProfile().followSetNotes
|
val userFollowSets = account.userProfile().followSetNotes
|
||||||
if (userFollowSets.isEmpty()) {
|
if (userFollowSets.isEmpty()) {
|
||||||
account.scope.launch {
|
|
||||||
try {
|
try {
|
||||||
account.getFollowSetNotes()
|
account.getFollowSetNotes()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -43,8 +44,10 @@ class FollowSetFeedFilter(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
userFollowSets.map { account.mapNoteToFollowSet(it) }.forEach {
|
||||||
|
followSetCache.add(it)
|
||||||
}
|
}
|
||||||
val followSets = userFollowSets.map { account.mapNoteToFollowSet(it) }
|
}
|
||||||
return followSets
|
return followSetCache.toList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,116 +23,19 @@ package com.vitorpamplona.amethyst.ui.screen
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.FollowSetFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.FollowSetFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.GeoHashFeedFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.dal.NIP90ContentDiscoveryResponseFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedContentState
|
import com.vitorpamplona.amethyst.ui.feeds.FeedContentState
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent
|
import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.NostrListFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.NostrListFeedViewModel
|
||||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class NostrChannelFeedViewModel(
|
|
||||||
val channel: Channel,
|
|
||||||
val account: Account,
|
|
||||||
) : FeedViewModel(ChannelFeedFilter(channel, account)) {
|
|
||||||
class Factory(
|
|
||||||
val channel: Channel,
|
|
||||||
val account: Account,
|
|
||||||
) : ViewModelProvider.Factory {
|
|
||||||
override fun <NostrChannelFeedViewModel : ViewModel> create(modelClass: Class<NostrChannelFeedViewModel>): NostrChannelFeedViewModel = NostrChannelFeedViewModel(channel, account) as NostrChannelFeedViewModel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NostrChatroomFeedViewModel(
|
|
||||||
val user: ChatroomKey,
|
|
||||||
val account: Account,
|
|
||||||
) : FeedViewModel(ChatroomFeedFilter(user, account)) {
|
|
||||||
class Factory(
|
|
||||||
val user: ChatroomKey,
|
|
||||||
val account: Account,
|
|
||||||
) : ViewModelProvider.Factory {
|
|
||||||
override fun <NostrChatRoomFeedViewModel : ViewModel> create(modelClass: Class<NostrChatRoomFeedViewModel>): NostrChatRoomFeedViewModel = NostrChatroomFeedViewModel(user, account) as NostrChatRoomFeedViewModel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NostrThreadFeedViewModel(
|
|
||||||
account: Account,
|
|
||||||
noteId: String,
|
|
||||||
) : LevelFeedViewModel(ThreadFeedFilter(account, noteId)) {
|
|
||||||
class Factory(
|
|
||||||
val account: Account,
|
|
||||||
val noteId: String,
|
|
||||||
) : ViewModelProvider.Factory {
|
|
||||||
override fun <NostrThreadFeedViewModel : ViewModel> create(modelClass: Class<NostrThreadFeedViewModel>): NostrThreadFeedViewModel = NostrThreadFeedViewModel(account, noteId) as NostrThreadFeedViewModel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NostrHashtagFeedViewModel(
|
|
||||||
val hashtag: String,
|
|
||||||
val account: Account,
|
|
||||||
) : FeedViewModel(HashtagFeedFilter(hashtag, account)) {
|
|
||||||
class Factory(
|
|
||||||
val hashtag: String,
|
|
||||||
val account: Account,
|
|
||||||
) : ViewModelProvider.Factory {
|
|
||||||
override fun <NostrHashtagFeedViewModel : ViewModel> create(modelClass: Class<NostrHashtagFeedViewModel>): NostrHashtagFeedViewModel = NostrHashtagFeedViewModel(hashtag, account) as NostrHashtagFeedViewModel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NostrGeoHashFeedViewModel(
|
|
||||||
val geohash: String,
|
|
||||||
val account: Account,
|
|
||||||
) : FeedViewModel(GeoHashFeedFilter(geohash, account)) {
|
|
||||||
class Factory(
|
|
||||||
val geohash: String,
|
|
||||||
val account: Account,
|
|
||||||
) : ViewModelProvider.Factory {
|
|
||||||
override fun <NostrGeoHashFeedViewModel : ViewModel> create(modelClass: Class<NostrGeoHashFeedViewModel>): NostrGeoHashFeedViewModel = NostrGeoHashFeedViewModel(geohash, account) as NostrGeoHashFeedViewModel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NostrCommunityFeedViewModel(
|
|
||||||
val note: AddressableNote,
|
|
||||||
val account: Account,
|
|
||||||
) : FeedViewModel(CommunityFeedFilter(note, account)) {
|
|
||||||
class Factory(
|
|
||||||
val note: AddressableNote,
|
|
||||||
val account: Account,
|
|
||||||
) : ViewModelProvider.Factory {
|
|
||||||
override fun <NostrCommunityFeedViewModel : ViewModel> create(modelClass: Class<NostrCommunityFeedViewModel>): NostrCommunityFeedViewModel = NostrCommunityFeedViewModel(note, account) as NostrCommunityFeedViewModel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
class NostrBookmarkPublicFeedViewModel(
|
|
||||||
val account: Account,
|
|
||||||
) : FeedViewModel(BookmarkPublicFeedFilter(account)) {
|
|
||||||
class Factory(
|
|
||||||
val account: Account,
|
|
||||||
) : ViewModelProvider.Factory {
|
|
||||||
override fun <NostrBookmarkPublicFeedViewModel : ViewModel> create(modelClass: Class<NostrBookmarkPublicFeedViewModel>): NostrBookmarkPublicFeedViewModel = NostrBookmarkPublicFeedViewModel(account) as NostrBookmarkPublicFeedViewModel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
class NostrBookmarkPrivateFeedViewModel(
|
|
||||||
val account: Account,
|
|
||||||
) : FeedViewModel(BookmarkPrivateFeedFilter(account)) {
|
|
||||||
class Factory(
|
|
||||||
val account: Account,
|
|
||||||
) : ViewModelProvider.Factory {
|
|
||||||
override fun <NostrBookmarkPrivateFeedViewModel : ViewModel> create(modelClass: Class<NostrBookmarkPrivateFeedViewModel>): NostrBookmarkPrivateFeedViewModel = NostrBookmarkPrivateFeedViewModel(account) as NostrBookmarkPrivateFeedViewModel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class NostrUserListFeedViewModel(
|
class NostrUserListFeedViewModel(
|
||||||
val account: Account,
|
val account: Account,
|
||||||
@@ -144,82 +47,6 @@ class NostrUserListFeedViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
|
||||||
class NostrNIP90ContentDiscoveryFeedViewModel(
|
|
||||||
val account: Account,
|
|
||||||
dvmkey: String,
|
|
||||||
requestid: String,
|
|
||||||
) : FeedViewModel(NIP90ContentDiscoveryResponseFilter(account, dvmkey, requestid)) {
|
|
||||||
class Factory(
|
|
||||||
val account: Account,
|
|
||||||
val dvmkey: String,
|
|
||||||
val requestid: String,
|
|
||||||
) : ViewModelProvider.Factory {
|
|
||||||
override fun <NostrNIP90ContentDiscoveryFeedViewModel : ViewModel> create(modelClass: Class<NostrNIP90ContentDiscoveryFeedViewModel>): NostrNIP90ContentDiscoveryFeedViewModel = NostrNIP90ContentDiscoveryFeedViewModel(account, dvmkey, requestid) as NostrNIP90ContentDiscoveryFeedViewModel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
class NostrDraftEventsFeedViewModel(
|
|
||||||
val account: Account,
|
|
||||||
) : FeedViewModel(DraftEventsFeedFilter(account)) {
|
|
||||||
class Factory(
|
|
||||||
val account: Account,
|
|
||||||
) : ViewModelProvider.Factory {
|
|
||||||
override fun <NostrDraftEventsFeedViewModel : ViewModel> create(modelClass: Class<NostrDraftEventsFeedViewModel>): NostrDraftEventsFeedViewModel = NostrDraftEventsFeedViewModel(account) as NostrDraftEventsFeedViewModel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class LevelFeedViewModel(
|
|
||||||
localFilter: FeedFilter<Note>,
|
|
||||||
) : FeedViewModel(localFilter) {
|
|
||||||
var llState: LazyListState by mutableStateOf(LazyListState(0, 0))
|
|
||||||
|
|
||||||
val hasDragged = mutableStateOf(false)
|
|
||||||
|
|
||||||
val selectedIDHex =
|
|
||||||
llState.interactionSource.interactions
|
|
||||||
.onEach {
|
|
||||||
if (it is DragInteraction.Start) {
|
|
||||||
hasDragged.value = true
|
|
||||||
}
|
|
||||||
}.stateIn(
|
|
||||||
viewModelScope,
|
|
||||||
SharingStarted.Eagerly,
|
|
||||||
null,
|
|
||||||
)
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
val levelCacheFlow: StateFlow<Map<Note, Int>> =
|
|
||||||
feedState.feedContent
|
|
||||||
.transformLatest { feed ->
|
|
||||||
emitAll(
|
|
||||||
if (feed is FeedState.Loaded) {
|
|
||||||
feed.feed.map {
|
|
||||||
val cache = mutableMapOf<Note, Int>()
|
|
||||||
it.list.forEach {
|
|
||||||
ThreadLevelCalculator.replyLevel(it, cache)
|
|
||||||
}
|
|
||||||
cache
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MutableStateFlow(mapOf())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}.flowOn(Dispatchers.Default)
|
|
||||||
.stateIn(
|
|
||||||
viewModelScope,
|
|
||||||
SharingStarted.WhileSubscribed(5000),
|
|
||||||
mapOf(),
|
|
||||||
)
|
|
||||||
|
|
||||||
fun levelFlowForItem(note: Note) =
|
|
||||||
levelCacheFlow
|
|
||||||
.map {
|
|
||||||
it[note] ?: 0
|
|
||||||
}.distinctUntilChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
abstract class FeedViewModel(
|
abstract class FeedViewModel(
|
||||||
localFilter: FeedFilter<Note>,
|
localFilter: FeedFilter<Note>,
|
||||||
|
|||||||
@@ -177,25 +177,9 @@ class AccountViewModel(
|
|||||||
|
|
||||||
var firstRoute: Route? = null
|
var firstRoute: Route? = null
|
||||||
|
|
||||||
// TODO: contact lists are not notes yet
|
val toastManager = ToastManager()
|
||||||
// val kind3Relays: StateFlow<ContactListEvent?> = observeByAuthor(ContactListEvent.KIND, account.signer.pubKey)
|
|
||||||
|
|
||||||
val normalizedKind3RelaySetFlow =
|
val feedStates = AccountFeedContentStates(this)
|
||||||
account
|
|
||||||
.userProfile()
|
|
||||||
.flow()
|
|
||||||
.relays.stateFlow
|
|
||||||
.map { contactListState ->
|
|
||||||
checkNotInMainThread()
|
|
||||||
contactListState.user.latestContactList?.relays()?.map {
|
|
||||||
RelayUrlFormatter.normalize(it.key)
|
|
||||||
} ?: emptySet()
|
|
||||||
}.flowOn(Dispatchers.Default)
|
|
||||||
.stateIn(
|
|
||||||
viewModelScope,
|
|
||||||
SharingStarted.WhileSubscribed(10000, 10000),
|
|
||||||
emptySet(),
|
|
||||||
)
|
|
||||||
|
|
||||||
val followSetsFlow =
|
val followSetsFlow =
|
||||||
account
|
account
|
||||||
@@ -215,13 +199,6 @@ class AccountViewModel(
|
|||||||
emptyList(),
|
emptyList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
val dmRelays: StateFlow<ChatMessageRelayListEvent?> = observeByAuthor(ChatMessageRelayListEvent.KIND, account.signer.pubKey)
|
|
||||||
val searchRelays: StateFlow<SearchRelayListEvent?> = observeByAuthor(SearchRelayListEvent.KIND, account.signer.pubKey)
|
|
||||||
|
|
||||||
val toastManager = ToastManager()
|
|
||||||
|
|
||||||
val feedStates = AccountFeedContentStates(this)
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
val notificationHasNewItems =
|
val notificationHasNewItems =
|
||||||
combineTransform(
|
combineTransform(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2024 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* 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
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
@@ -63,18 +63,18 @@ import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
|||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedError
|
import com.vitorpamplona.amethyst.ui.feeds.FeedError
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed
|
import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.TopBarWithBackButton
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrUserListFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrUserListFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.DisappearingScaffold
|
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
|
import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ fun ListsScreen(
|
|||||||
},
|
},
|
||||||
openItem = {
|
openItem = {
|
||||||
currentCoroutineScope.launch(Dispatchers.IO) {
|
currentCoroutineScope.launch(Dispatchers.IO) {
|
||||||
val note = followSetsViewModel.getFollowSetAddressable(it, accountViewModel.account)
|
val note = followSetsViewModel.getFollowSetNote(it, accountViewModel.account)
|
||||||
if (note != null) {
|
if (note != null) {
|
||||||
val event = note.event as PeopleListEvent
|
val event = note.event as PeopleListEvent
|
||||||
println("Found list, with title: ${event.nameOrTitle()}")
|
println("Found list, with title: ${event.nameOrTitle()}")
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2024 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* 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
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
@@ -21,8 +21,9 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.value
|
||||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||||
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
|
import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class FollowSet(
|
data class FollowSet(
|
||||||
@@ -33,7 +34,7 @@ data class FollowSet(
|
|||||||
val profileList: Set<String>,
|
val profileList: Set<String>,
|
||||||
) : NostrList(listVisibility = visibility, content = profileList) {
|
) : NostrList(listVisibility = visibility, content = profileList) {
|
||||||
companion object {
|
companion object {
|
||||||
fun mapEventToSet(
|
suspend fun mapEventToSet(
|
||||||
event: PeopleListEvent,
|
event: PeopleListEvent,
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
): FollowSet {
|
): FollowSet {
|
||||||
@@ -41,9 +42,12 @@ data class FollowSet(
|
|||||||
val dTag = event.dTag()
|
val dTag = event.dTag()
|
||||||
val listTitle = event.nameOrTitle() ?: dTag
|
val listTitle = event.nameOrTitle() ?: dTag
|
||||||
val listDescription = event.description() ?: ""
|
val listDescription = event.description() ?: ""
|
||||||
val publicFollows = event.filterTagList("p", null)
|
val publicFollows = event.publicPeople().map { it.toTagArray() }.map { it.value() }
|
||||||
val privateFollows = mutableListOf<String>()
|
val privateFollows =
|
||||||
event.privateTaggedUsers(signer) { userList -> privateFollows.addAll(userList) }
|
event
|
||||||
|
.privatePeople(signer)
|
||||||
|
?.map { it.toTagArray() }
|
||||||
|
?.map { it.value() } ?: emptyList()
|
||||||
return if (publicFollows.isEmpty() && privateFollows.isNotEmpty()) {
|
return if (publicFollows.isEmpty() && privateFollows.isNotEmpty()) {
|
||||||
FollowSet(
|
FollowSet(
|
||||||
identifierTag = address.toValue(),
|
identifierTag = address.toValue(),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2024 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* 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
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2024 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* 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
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2024 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* 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
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2024 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* 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
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
@@ -22,18 +22,22 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.FollowSetFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent
|
import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists
|
||||||
import com.vitorpamplona.ammolite.relays.BundledUpdate
|
import com.vitorpamplona.ammolite.relays.BundledUpdate
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
||||||
|
import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -42,9 +46,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
// TODO: Investigate the addition of feed filters, for bookmark sets and general ones.
|
// TODO: Investigate the addition of feed filters, for bookmark sets and general ones.
|
||||||
abstract class NostrListFeedViewModel(
|
open class NostrListFeedViewModel(
|
||||||
val dataSource: FeedFilter<FollowSet>,
|
val dataSource: FeedFilter<FollowSet>,
|
||||||
) : ViewModel(),
|
) : ViewModel(),
|
||||||
InvalidatableContent {
|
InvalidatableContent {
|
||||||
@@ -57,15 +62,28 @@ abstract class NostrListFeedViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFollowSetAddressable(
|
fun getFollowSetNote(
|
||||||
addressValue: String,
|
noteIdentifier: String,
|
||||||
account: Account,
|
account: Account,
|
||||||
): AddressableNote? {
|
): AddressableNote? {
|
||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
val potentialNote = LocalCache.getAddressableNoteIfExists(Address.parse(addressValue)!!)
|
val potentialNote = account.userProfile().followSetNotes.find { it.dTag() == noteIdentifier }
|
||||||
return potentialNote
|
return potentialNote
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun followSetExistsWithName(
|
||||||
|
setName: String,
|
||||||
|
account: Account,
|
||||||
|
): Boolean {
|
||||||
|
checkNotInMainThread()
|
||||||
|
val potentialNote =
|
||||||
|
account
|
||||||
|
.userProfile()
|
||||||
|
.followSetNotes
|
||||||
|
.find { (it.event as PeopleListEvent).nameOrTitle() == setName }
|
||||||
|
return potentialNote != null
|
||||||
|
}
|
||||||
|
|
||||||
override val isRefreshing: MutableState<Boolean> = mutableStateOf(false)
|
override val isRefreshing: MutableState<Boolean> = mutableStateOf(false)
|
||||||
|
|
||||||
private fun refreshSuspended() {
|
private fun refreshSuspended() {
|
||||||
@@ -96,6 +114,111 @@ abstract class NostrListFeedViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addFollowSet(
|
||||||
|
setName: String,
|
||||||
|
setDescription: String?,
|
||||||
|
setType: ListVisibility,
|
||||||
|
account: Account,
|
||||||
|
) {
|
||||||
|
if (account.settings.isWriteable()) {
|
||||||
|
println("You are in read-only mode. Please login to make modifications.")
|
||||||
|
} else {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
PeopleListEvent.createListWithDescription(
|
||||||
|
dTag = UUID.randomUUID().toString(),
|
||||||
|
title = setName,
|
||||||
|
description = setDescription,
|
||||||
|
isPrivate = setType == ListVisibility.Private,
|
||||||
|
signer = account.signer,
|
||||||
|
) {
|
||||||
|
account.sendMyPublicAndPrivateOutbox(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun renameFollowSet(
|
||||||
|
newName: String,
|
||||||
|
followSet: FollowSet,
|
||||||
|
account: Account,
|
||||||
|
) {
|
||||||
|
if (!account.settings.isWriteable()) {
|
||||||
|
println("You are in read-only mode. Please login to make modifications.")
|
||||||
|
} else {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val setEvent = getFollowSetNote(followSet.identifierTag, account)?.event as PeopleListEvent
|
||||||
|
PeopleListEvent.modifyListName(
|
||||||
|
earlierVersion = setEvent,
|
||||||
|
newName = newName,
|
||||||
|
signer = account.signer,
|
||||||
|
) {
|
||||||
|
account.sendMyPublicAndPrivateOutbox(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteFollowSet(
|
||||||
|
followSet: FollowSet,
|
||||||
|
account: Account,
|
||||||
|
) {
|
||||||
|
if (!account.settings.isWriteable()) {
|
||||||
|
println("You are in read-only mode. Please login to make modifications.")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val followSetEvent = getFollowSetNote(followSet.identifierTag, account)?.event as PeopleListEvent
|
||||||
|
val deletionEvent = account.signer.sign(DeletionEvent.build(listOf(followSetEvent)))
|
||||||
|
account.sendMyPublicAndPrivateOutbox(deletionEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addUserToSet(
|
||||||
|
userProfileHex: String,
|
||||||
|
followSet: FollowSet,
|
||||||
|
account: Account,
|
||||||
|
) {
|
||||||
|
if (!account.settings.isWriteable()) {
|
||||||
|
println("You are in read-only mode. Please login to make modifications.")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val followSetEvent = getFollowSetNote(followSet.identifierTag, account)?.event as PeopleListEvent
|
||||||
|
PeopleListEvent.addUser(
|
||||||
|
earlierVersion = followSetEvent,
|
||||||
|
pubKeyHex = userProfileHex,
|
||||||
|
isPrivate = followSet.visibility == ListVisibility.Private,
|
||||||
|
signer = account.signer,
|
||||||
|
) {
|
||||||
|
account.sendMyPublicAndPrivateOutbox(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeUserFromSet(
|
||||||
|
userProfileHex: String,
|
||||||
|
followSet: FollowSet,
|
||||||
|
account: Account,
|
||||||
|
) {
|
||||||
|
if (!account.settings.isWriteable()) {
|
||||||
|
println("You are in read-only mode. Please login to make modifications.")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val followSetEvent = getFollowSetNote(followSet.identifierTag, account)?.event as PeopleListEvent
|
||||||
|
PeopleListEvent.removeUser(
|
||||||
|
earlierVersion = followSetEvent,
|
||||||
|
pubKeyHex = userProfileHex,
|
||||||
|
signer = account.signer,
|
||||||
|
) {
|
||||||
|
account.sendMyPublicAndPrivateOutbox(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateFeed(sets: ImmutableList<FollowSet>) {
|
private fun updateFeed(sets: ImmutableList<FollowSet>) {
|
||||||
if (sets.isNotEmpty()) {
|
if (sets.isNotEmpty()) {
|
||||||
_feedContent.update { FollowSetState.Loaded(sets) }
|
_feedContent.update { FollowSetState.Loaded(sets) }
|
||||||
@@ -104,13 +227,14 @@ abstract class NostrListFeedViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val bundler = BundledUpdate(1000, Dispatchers.IO)
|
private val bundler = BundledUpdate(2000, Dispatchers.IO)
|
||||||
|
|
||||||
override fun invalidateData(ignoreIfDoing: Boolean) {
|
override fun invalidateData(ignoreIfDoing: Boolean) {
|
||||||
// refresh()
|
// refresh()
|
||||||
bundler.invalidate(ignoreIfDoing) {
|
bundler.invalidate(ignoreIfDoing) {
|
||||||
// adds the time to perform the refresh into this delay
|
// adds the time to perform the refresh into this delay
|
||||||
// holding off new updates in case of heavy refresh routines.
|
// holding off new updates in case of heavy refresh routines.
|
||||||
|
|
||||||
refreshSuspended()
|
refreshSuspended()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,4 +259,11 @@ abstract class NostrListFeedViewModel(
|
|||||||
collectorJob?.cancel()
|
collectorJob?.cancel()
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class Factory(
|
||||||
|
val account: Account,
|
||||||
|
) : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T = NostrListFeedViewModel(FollowSetFeedFilter(account)) as T
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2024 Vitor Pamplona
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
*
|
*
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
* 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
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import com.vitorpamplona.quartz.nip51Lists.encryption.PrivateTagsInContent
|
|||||||
import com.vitorpamplona.quartz.nip51Lists.muteList.tags.MuteTag
|
import com.vitorpamplona.quartz.nip51Lists.muteList.tags.MuteTag
|
||||||
import com.vitorpamplona.quartz.nip51Lists.muteList.tags.UserTag
|
import com.vitorpamplona.quartz.nip51Lists.muteList.tags.UserTag
|
||||||
import com.vitorpamplona.quartz.nip51Lists.remove
|
import com.vitorpamplona.quartz.nip51Lists.remove
|
||||||
|
import com.vitorpamplona.quartz.nip51Lists.replaceAll
|
||||||
import com.vitorpamplona.quartz.nip51Lists.tags.DescriptionTag
|
import com.vitorpamplona.quartz.nip51Lists.tags.DescriptionTag
|
||||||
import com.vitorpamplona.quartz.nip51Lists.tags.NameTag
|
import com.vitorpamplona.quartz.nip51Lists.tags.NameTag
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
@@ -214,5 +215,138 @@ class PeopleListEvent(
|
|||||||
|
|
||||||
initializer()
|
initializer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun createListWithDescription(
|
||||||
|
dTag: String,
|
||||||
|
title: String,
|
||||||
|
description: String? = null,
|
||||||
|
isPrivate: Boolean,
|
||||||
|
signer: NostrSigner,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
onReady: (PeopleListEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
if (description == null) {
|
||||||
|
val newList =
|
||||||
|
create(
|
||||||
|
name = title,
|
||||||
|
person = UserTag(pubKey = signer.pubKey),
|
||||||
|
isPrivate = isPrivate,
|
||||||
|
signer = signer,
|
||||||
|
dTag = dTag,
|
||||||
|
createdAt = createdAt,
|
||||||
|
)
|
||||||
|
onReady(newList)
|
||||||
|
} else {
|
||||||
|
if (isPrivate) {
|
||||||
|
val event =
|
||||||
|
build(
|
||||||
|
name = title,
|
||||||
|
privatePeople = listOf(UserTag(pubKey = signer.pubKey)),
|
||||||
|
signer = signer,
|
||||||
|
dTag = dTag,
|
||||||
|
createdAt = createdAt,
|
||||||
|
) {
|
||||||
|
addUnique(arrayOf("description", description))
|
||||||
|
}
|
||||||
|
val list = signer.sign(event)
|
||||||
|
onReady(list)
|
||||||
|
} else {
|
||||||
|
val event =
|
||||||
|
build(
|
||||||
|
name = title,
|
||||||
|
publicPeople = listOf(UserTag(pubKey = signer.pubKey)),
|
||||||
|
signer = signer,
|
||||||
|
dTag = dTag,
|
||||||
|
createdAt = createdAt,
|
||||||
|
) {
|
||||||
|
addUnique(arrayOf("description", description))
|
||||||
|
}
|
||||||
|
val list = signer.sign(event)
|
||||||
|
onReady(list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun createListWithUser(
|
||||||
|
name: String,
|
||||||
|
pubKeyHex: String,
|
||||||
|
isPrivate: Boolean,
|
||||||
|
signer: NostrSigner,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
onReady: (PeopleListEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
val newList =
|
||||||
|
create(
|
||||||
|
name = name,
|
||||||
|
person = UserTag(pubKey = pubKeyHex),
|
||||||
|
isPrivate = isPrivate,
|
||||||
|
signer = signer,
|
||||||
|
createdAt = createdAt,
|
||||||
|
)
|
||||||
|
onReady(newList)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun addUser(
|
||||||
|
earlierVersion: PeopleListEvent,
|
||||||
|
pubKeyHex: String,
|
||||||
|
isPrivate: Boolean,
|
||||||
|
signer: NostrSigner,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
onReady: (PeopleListEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
val newList =
|
||||||
|
add(
|
||||||
|
earlierVersion = earlierVersion,
|
||||||
|
person = UserTag(pubKey = pubKeyHex),
|
||||||
|
isPrivate = isPrivate,
|
||||||
|
signer = signer,
|
||||||
|
createdAt = createdAt,
|
||||||
|
)
|
||||||
|
onReady(newList)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun removeUser(
|
||||||
|
earlierVersion: PeopleListEvent,
|
||||||
|
pubKeyHex: String,
|
||||||
|
signer: NostrSigner,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
onReady: (PeopleListEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
val updatedList =
|
||||||
|
remove(
|
||||||
|
earlierVersion = earlierVersion,
|
||||||
|
person = UserTag(pubKey = pubKeyHex),
|
||||||
|
signer = signer,
|
||||||
|
createdAt = createdAt,
|
||||||
|
)
|
||||||
|
onReady(updatedList)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun modifyListName(
|
||||||
|
earlierVersion: PeopleListEvent,
|
||||||
|
newName: String,
|
||||||
|
signer: NostrSigner,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
onReady: (PeopleListEvent) -> Unit = {},
|
||||||
|
) {
|
||||||
|
val privateTags = earlierVersion.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException()
|
||||||
|
val currentTitle = earlierVersion.tags.first { it[0] == NameTag.TAG_NAME || it[0] == TitleTag.TAG_NAME }
|
||||||
|
val newTitleTag =
|
||||||
|
if (currentTitle[0] == NameTag.TAG_NAME) {
|
||||||
|
NameTag.assemble(newName)
|
||||||
|
} else {
|
||||||
|
com.vitorpamplona.quartz.nip51Lists.tags.TitleTag
|
||||||
|
.assemble(newName)
|
||||||
|
}
|
||||||
|
|
||||||
|
val modified =
|
||||||
|
resign(
|
||||||
|
publicTags = earlierVersion.tags.replaceAll(currentTitle, newTitleTag),
|
||||||
|
privateTags = privateTags.replaceAll(currentTitle, newTitleTag),
|
||||||
|
signer = signer,
|
||||||
|
createdAt = createdAt,
|
||||||
|
)
|
||||||
|
onReady(modified)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user