add NIP90 status events, update NIP90 feeds based on request id
This commit is contained in:
@@ -91,6 +91,7 @@ import com.vitorpamplona.quartz.events.MetadataEvent
|
|||||||
import com.vitorpamplona.quartz.events.MuteListEvent
|
import com.vitorpamplona.quartz.events.MuteListEvent
|
||||||
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryRequestEvent
|
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryRequestEvent
|
||||||
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent
|
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent
|
||||||
|
import com.vitorpamplona.quartz.events.NIP90StatusEvent
|
||||||
import com.vitorpamplona.quartz.events.NIP90UserDiscoveryRequestEvent
|
import com.vitorpamplona.quartz.events.NIP90UserDiscoveryRequestEvent
|
||||||
import com.vitorpamplona.quartz.events.NIP90UserDiscoveryResponseEvent
|
import com.vitorpamplona.quartz.events.NIP90UserDiscoveryResponseEvent
|
||||||
import com.vitorpamplona.quartz.events.NNSEvent
|
import com.vitorpamplona.quartz.events.NNSEvent
|
||||||
@@ -444,6 +445,34 @@ object LocalCache {
|
|||||||
refreshObservers(note)
|
refreshObservers(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun consume(
|
||||||
|
event: NIP90StatusEvent,
|
||||||
|
relay: Relay? = null,
|
||||||
|
) {
|
||||||
|
val note = getOrCreateNote(event.id)
|
||||||
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
|
if (relay != null) {
|
||||||
|
author.addRelayBeingUsed(relay, event.createdAt)
|
||||||
|
note.addRelay(relay)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Already processed this event.
|
||||||
|
if (note.event != null) return
|
||||||
|
|
||||||
|
val replyTo = computeReplyTo(event)
|
||||||
|
|
||||||
|
note.loadEvent(event, author, replyTo)
|
||||||
|
|
||||||
|
// Log.d("TN", "New Note (${notes.size},${users.size}) ${note.author?.toBestDisplayName()}
|
||||||
|
// ${note.event?.content()?.split("\n")?.take(100)} ${formattedDateTime(event.createdAt)}")
|
||||||
|
|
||||||
|
// Counts the replies
|
||||||
|
replyTo.forEach { it.addReply(note) }
|
||||||
|
|
||||||
|
refreshObservers(note)
|
||||||
|
}
|
||||||
|
|
||||||
fun consume(
|
fun consume(
|
||||||
event: NIP90UserDiscoveryResponseEvent,
|
event: NIP90UserDiscoveryResponseEvent,
|
||||||
relay: Relay? = null,
|
relay: Relay? = null,
|
||||||
@@ -2415,6 +2444,7 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
is LnZapRequestEvent -> consume(event)
|
is LnZapRequestEvent -> consume(event)
|
||||||
|
is NIP90StatusEvent -> consume(event, relay)
|
||||||
is NIP90ContentDiscoveryResponseEvent -> consume(event, relay)
|
is NIP90ContentDiscoveryResponseEvent -> consume(event, relay)
|
||||||
is NIP90ContentDiscoveryRequestEvent -> consume(event, relay)
|
is NIP90ContentDiscoveryRequestEvent -> consume(event, relay)
|
||||||
is NIP90UserDiscoveryResponseEvent -> consume(event, relay)
|
is NIP90UserDiscoveryResponseEvent -> consume(event, relay)
|
||||||
|
|||||||
@@ -159,7 +159,25 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") {
|
|||||||
types = setOf(FeedType.GLOBAL),
|
types = setOf(FeedType.GLOBAL),
|
||||||
filter =
|
filter =
|
||||||
JsonFilter(
|
JsonFilter(
|
||||||
kinds = listOf(NIP90ContentDiscoveryResponseEvent.KIND, NIP90StatusEvent.KIND),
|
kinds = listOf(NIP90ContentDiscoveryResponseEvent.KIND),
|
||||||
|
limit = 300,
|
||||||
|
since =
|
||||||
|
latestEOSEs.users[account.userProfile()]
|
||||||
|
?.followList
|
||||||
|
?.get(account.defaultDiscoveryFollowList.value)
|
||||||
|
?.relayList,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createNIP90StatusFilter(): List<TypedFilter> {
|
||||||
|
return listOfNotNull(
|
||||||
|
TypedFilter(
|
||||||
|
types = setOf(FeedType.GLOBAL),
|
||||||
|
filter =
|
||||||
|
JsonFilter(
|
||||||
|
kinds = listOf(NIP90StatusEvent.KIND),
|
||||||
limit = 300,
|
limit = 300,
|
||||||
since =
|
since =
|
||||||
latestEOSEs.users[account.userProfile()]
|
latestEOSEs.users[account.userProfile()]
|
||||||
@@ -446,6 +464,7 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") {
|
|||||||
createLiveStreamFilter()
|
createLiveStreamFilter()
|
||||||
.plus(createNIP89Filter(listOf("5300")))
|
.plus(createNIP89Filter(listOf("5300")))
|
||||||
.plus(createNIP90ResponseFilter())
|
.plus(createNIP90ResponseFilter())
|
||||||
|
.plus(createNIP90StatusFilter())
|
||||||
.plus(createPublicChatFilter())
|
.plus(createPublicChatFilter())
|
||||||
.plus(createMarketplaceFilter())
|
.plus(createMarketplaceFilter())
|
||||||
.plus(
|
.plus(
|
||||||
|
|||||||
@@ -26,10 +26,14 @@ import com.vitorpamplona.amethyst.model.Note
|
|||||||
import com.vitorpamplona.quartz.events.AppDefinitionEvent
|
import com.vitorpamplona.quartz.events.AppDefinitionEvent
|
||||||
import com.vitorpamplona.quartz.events.MuteListEvent
|
import com.vitorpamplona.quartz.events.MuteListEvent
|
||||||
import com.vitorpamplona.quartz.events.PeopleListEvent
|
import com.vitorpamplona.quartz.events.PeopleListEvent
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
open class DiscoverNIP89FeedFilter(
|
open class DiscoverNIP89FeedFilter(
|
||||||
val account: Account,
|
val account: Account,
|
||||||
) : AdditiveFeedFilter<Note>() {
|
) : AdditiveFeedFilter<Note>() {
|
||||||
|
val lastAnnounced = 90 * 24 * 60 * 60 // 90 Days ago
|
||||||
|
// TODO better than announced would be last active, as this requires the DVM provider to regularly update the NIP89 announcement
|
||||||
|
|
||||||
override fun feedKey(): String {
|
override fun feedKey(): String {
|
||||||
return account.userProfile().pubkeyHex + "-" + followList()
|
return account.userProfile().pubkeyHex + "-" + followList()
|
||||||
}
|
}
|
||||||
@@ -49,7 +53,7 @@ open class DiscoverNIP89FeedFilter(
|
|||||||
val notes =
|
val notes =
|
||||||
LocalCache.addressables.filterIntoSet { _, it ->
|
LocalCache.addressables.filterIntoSet { _, it ->
|
||||||
val noteEvent = it.event
|
val noteEvent = it.event
|
||||||
noteEvent is AppDefinitionEvent // && params.match(noteEvent)
|
noteEvent is AppDefinitionEvent && noteEvent.createdAt > TimeUtils.now() - lastAnnounced // && params.match(noteEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sort(notes)
|
return sort(notes)
|
||||||
@@ -73,7 +77,7 @@ open class DiscoverNIP89FeedFilter(
|
|||||||
|
|
||||||
return collection.filterTo(HashSet()) {
|
return collection.filterTo(HashSet()) {
|
||||||
val noteEvent = it.event
|
val noteEvent = it.event
|
||||||
noteEvent is AppDefinitionEvent // && params.match(noteEvent)
|
noteEvent is AppDefinitionEvent && noteEvent.createdAt > TimeUtils.now() - lastAnnounced // && params.match(noteEvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-10
@@ -25,7 +25,6 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|||||||
import com.vitorpamplona.amethyst.model.Account
|
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.quartz.encoders.toHexKey
|
|
||||||
import com.vitorpamplona.quartz.events.MuteListEvent
|
import com.vitorpamplona.quartz.events.MuteListEvent
|
||||||
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent
|
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent
|
||||||
import com.vitorpamplona.quartz.events.PeopleListEvent
|
import com.vitorpamplona.quartz.events.PeopleListEvent
|
||||||
@@ -33,9 +32,10 @@ import com.vitorpamplona.quartz.events.PeopleListEvent
|
|||||||
open class NIP90ContentDiscoveryFilter(
|
open class NIP90ContentDiscoveryFilter(
|
||||||
val account: Account,
|
val account: Account,
|
||||||
val dvmkey: String,
|
val dvmkey: String,
|
||||||
|
val request: String,
|
||||||
) : AdditiveFeedFilter<Note>() {
|
) : AdditiveFeedFilter<Note>() {
|
||||||
override fun feedKey(): String {
|
override fun feedKey(): String {
|
||||||
return account.userProfile().pubkeyHex + "-" + followList()
|
return account.userProfile().pubkeyHex + "-" + request
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun followList(): String {
|
open fun followList(): String {
|
||||||
@@ -53,8 +53,10 @@ open class NIP90ContentDiscoveryFilter(
|
|||||||
val notes =
|
val notes =
|
||||||
LocalCache.notes.filterIntoSet { _, it ->
|
LocalCache.notes.filterIntoSet { _, it ->
|
||||||
val noteEvent = it.event
|
val noteEvent = it.event
|
||||||
noteEvent is NIP90ContentDiscoveryResponseEvent && it.event?.pubKey() == dvmkey && it.event?.isTaggedUser(account.keyPair.pubKey.toHexKey()) == true // && params.match(noteEvent)
|
noteEvent is NIP90ContentDiscoveryResponseEvent && it.event!!.isTaggedEvent(request)
|
||||||
|
// it.event?.pubKey() == dvmkey && it.event?.isTaggedUser(account.keyPair.pubKey.toHexKey()) == true // && params.match(noteEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
var sorted = sort(notes)
|
var sorted = sort(notes)
|
||||||
if (sorted.isNotEmpty()) {
|
if (sorted.isNotEmpty()) {
|
||||||
var note = sorted.first()
|
var note = sorted.first()
|
||||||
@@ -99,16 +101,17 @@ open class NIP90ContentDiscoveryFilter(
|
|||||||
val notes =
|
val notes =
|
||||||
collection.filterTo(HashSet()) {
|
collection.filterTo(HashSet()) {
|
||||||
val noteEvent = it.event
|
val noteEvent = it.event
|
||||||
noteEvent is NIP90ContentDiscoveryResponseEvent && it.event?.isTaggedUser(account.keyPair.pubKey.toHexKey()) == true // && params.match(noteEvent)
|
noteEvent is NIP90ContentDiscoveryResponseEvent && // &&
|
||||||
|
it.event!!.isTaggedEvent(request) // && it.event?.isTaggedUser(account.keyPair.pubKey.toHexKey()) == true // && params.match(noteEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO. We want to parse the content of the latest event to ids and get the nodes from these ids
|
val sorted = sort(notes)
|
||||||
|
println("REQUEST: " + request)
|
||||||
var sorted = sort(notes)
|
|
||||||
if (sorted.isNotEmpty()) {
|
if (sorted.isNotEmpty()) {
|
||||||
var note = sorted.first()
|
var note = sorted.first()
|
||||||
|
|
||||||
var eventContent = note.event?.content()
|
var eventContent = note.event?.content()
|
||||||
|
println(eventContent)
|
||||||
|
|
||||||
val collection: HashSet<Note> = hashSetOf()
|
val collection: HashSet<Note> = hashSetOf()
|
||||||
val mapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
val mapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||||
@@ -118,7 +121,7 @@ open class NIP90ContentDiscoveryFilter(
|
|||||||
// TODO. This is ugly. how to Kotlin?
|
// TODO. This is ugly. how to Kotlin?
|
||||||
var id = element.toString().trimStart('[').trimStart('e').trimStart(',').trimEnd(']').trimStart().trimEnd()
|
var id = element.toString().trimStart('[').trimStart('e').trimStart(',').trimEnd(']').trimStart().trimEnd()
|
||||||
|
|
||||||
var note = LocalCache.checkGetOrCreateNote(id)
|
val note = LocalCache.checkGetOrCreateNote(id)
|
||||||
if (note != null) {
|
if (note != null) {
|
||||||
collection.add(note)
|
collection.add(note)
|
||||||
}
|
}
|
||||||
@@ -127,8 +130,6 @@ open class NIP90ContentDiscoveryFilter(
|
|||||||
} else {
|
} else {
|
||||||
return notes
|
return notes
|
||||||
}
|
}
|
||||||
|
|
||||||
return notes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sort(collection: Set<Note>): List<Note> {
|
override fun sort(collection: Set<Note>): List<Note> {
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* 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.dal
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.quartz.events.MuteListEvent
|
||||||
|
import com.vitorpamplona.quartz.events.NIP90StatusEvent
|
||||||
|
import com.vitorpamplona.quartz.events.PeopleListEvent
|
||||||
|
|
||||||
|
open class NIP90StatusFilter(
|
||||||
|
val account: Account,
|
||||||
|
val dvmkey: String,
|
||||||
|
val request: String,
|
||||||
|
) : AdditiveFeedFilter<Note>() {
|
||||||
|
override fun feedKey(): String {
|
||||||
|
return account.userProfile().pubkeyHex + "-" + followList()
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun followList(): String {
|
||||||
|
return account.defaultDiscoveryFollowList.value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showHiddenKey(): Boolean {
|
||||||
|
return followList() == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
|
||||||
|
followList() == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun feed(): List<Note> {
|
||||||
|
val params = buildFilterParams(account)
|
||||||
|
|
||||||
|
val status =
|
||||||
|
LocalCache.notes.filterIntoSet { _, it ->
|
||||||
|
val noteEvent = it.event
|
||||||
|
noteEvent is NIP90StatusEvent && it.event?.pubKey() == dvmkey &&
|
||||||
|
it.event!!.isTaggedEvent(request)
|
||||||
|
// && it.event?.isTaggedUser(account.keyPair.pubKey.toHexKey()) == true // && params.match(noteEvent)
|
||||||
|
}
|
||||||
|
if (status.isNotEmpty()) {
|
||||||
|
println("Found status")
|
||||||
|
return listOf(status.first())
|
||||||
|
} else {
|
||||||
|
println("Empty status")
|
||||||
|
return listOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun applyFilter(collection: Set<Note>): Set<Note> {
|
||||||
|
return innerApplyFilter(collection)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildFilterParams(account: Account): FilterByListParams {
|
||||||
|
return FilterByListParams.create(
|
||||||
|
account.userProfile().pubkeyHex,
|
||||||
|
account.defaultDiscoveryFollowList.value,
|
||||||
|
account.liveDiscoveryFollowLists.value,
|
||||||
|
account.flowHiddenUsers.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
||||||
|
// val params = buildFilterParams(account)
|
||||||
|
|
||||||
|
val status =
|
||||||
|
LocalCache.notes.filterIntoSet { _, it ->
|
||||||
|
val noteEvent = it.event
|
||||||
|
noteEvent is NIP90StatusEvent && it.event?.pubKey() == dvmkey &&
|
||||||
|
it.event!!.isTaggedEvent(request)
|
||||||
|
// && it.event?.isTaggedUser(account.keyPair.pubKey.toHexKey()) == true // && params.match(noteEvent)
|
||||||
|
}
|
||||||
|
if (status.isNotEmpty()) {
|
||||||
|
println("Found status")
|
||||||
|
return setOf(status.first())
|
||||||
|
} else {
|
||||||
|
println("Empty status")
|
||||||
|
return setOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sort(collection: Set<Note>): List<Note> {
|
||||||
|
return collection.toList() // collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -100,6 +100,7 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderLiveActivityChatMessage
|
|||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderLiveActivityEvent
|
import com.vitorpamplona.amethyst.ui.note.types.RenderLiveActivityEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderLongFormContent
|
import com.vitorpamplona.amethyst.ui.note.types.RenderLongFormContent
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderNIP90ContentDiscoveryResponse
|
import com.vitorpamplona.amethyst.ui.note.types.RenderNIP90ContentDiscoveryResponse
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.types.RenderNIP90Status
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderPinListEvent
|
import com.vitorpamplona.amethyst.ui.note.types.RenderPinListEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderPoll
|
import com.vitorpamplona.amethyst.ui.note.types.RenderPoll
|
||||||
import com.vitorpamplona.amethyst.ui.note.types.RenderPostApproval
|
import com.vitorpamplona.amethyst.ui.note.types.RenderPostApproval
|
||||||
@@ -163,6 +164,7 @@ import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent
|
|||||||
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
|
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
|
||||||
import com.vitorpamplona.quartz.events.LongTextNoteEvent
|
import com.vitorpamplona.quartz.events.LongTextNoteEvent
|
||||||
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent
|
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent
|
||||||
|
import com.vitorpamplona.quartz.events.NIP90StatusEvent
|
||||||
import com.vitorpamplona.quartz.events.PeopleListEvent
|
import com.vitorpamplona.quartz.events.PeopleListEvent
|
||||||
import com.vitorpamplona.quartz.events.PinListEvent
|
import com.vitorpamplona.quartz.events.PinListEvent
|
||||||
import com.vitorpamplona.quartz.events.PollNoteEvent
|
import com.vitorpamplona.quartz.events.PollNoteEvent
|
||||||
@@ -683,6 +685,19 @@ private fun RenderNoteRow(
|
|||||||
nav,
|
nav,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
is NIP90StatusEvent ->
|
||||||
|
RenderNIP90Status(
|
||||||
|
baseNote,
|
||||||
|
makeItShort,
|
||||||
|
canPreview,
|
||||||
|
quotesLeft,
|
||||||
|
unPackReply,
|
||||||
|
backgroundColor,
|
||||||
|
editState,
|
||||||
|
accountViewModel,
|
||||||
|
nav,
|
||||||
|
)
|
||||||
|
|
||||||
is PollNoteEvent -> {
|
is PollNoteEvent -> {
|
||||||
RenderPoll(
|
RenderPoll(
|
||||||
baseNote,
|
baseNote,
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* 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.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.LoadDecryptedContent
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.quartz.events.BaseTextNoteEvent
|
||||||
|
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
||||||
|
import com.vitorpamplona.quartz.events.TextNoteEvent
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RenderNIP90Status(
|
||||||
|
note: Note,
|
||||||
|
makeItShort: Boolean,
|
||||||
|
canPreview: Boolean,
|
||||||
|
quotesLeft: Int,
|
||||||
|
unPackReply: Boolean,
|
||||||
|
backgroundColor: MutableState<Color>,
|
||||||
|
editState: State<GenericLoadable<EditState>>,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
val noteEvent = note.event
|
||||||
|
val modifier = remember(note) { Modifier.fillMaxWidth() }
|
||||||
|
|
||||||
|
val showReply by
|
||||||
|
remember(note) {
|
||||||
|
derivedStateOf {
|
||||||
|
noteEvent is BaseTextNoteEvent && !makeItShort && unPackReply && (note.replyTo != null || noteEvent.hasAnyTaggedUser())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showReply) {
|
||||||
|
val replyingDirectlyTo =
|
||||||
|
remember(note) {
|
||||||
|
if (noteEvent is BaseTextNoteEvent) {
|
||||||
|
val replyingTo = noteEvent.replyingToAddressOrEvent()
|
||||||
|
if (replyingTo != null) {
|
||||||
|
val newNote = accountViewModel.getNoteIfExists(replyingTo)
|
||||||
|
if (newNote != null && newNote.channelHex() == null && newNote.event?.kind() != CommunityDefinitionEvent.KIND) {
|
||||||
|
newNote
|
||||||
|
} else {
|
||||||
|
note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.KIND }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.KIND }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
note.replyTo?.lastOrNull { it.event?.kind() != CommunityDefinitionEvent.KIND }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadDecryptedContent(
|
||||||
|
note,
|
||||||
|
accountViewModel,
|
||||||
|
) { body ->
|
||||||
|
val eventContent by
|
||||||
|
remember(note.event) {
|
||||||
|
derivedStateOf {
|
||||||
|
val subject = (note.event as? TextNoteEvent)?.subject()?.ifEmpty { null }
|
||||||
|
val newBody =
|
||||||
|
if (editState.value is GenericLoadable.Loaded) {
|
||||||
|
val state =
|
||||||
|
(editState.value as? GenericLoadable.Loaded)?.loaded?.modificationToShow
|
||||||
|
state?.value?.event?.content() ?: body
|
||||||
|
} else {
|
||||||
|
body
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!subject.isNullOrBlank() && !newBody.split("\n")[0].contains(subject)) {
|
||||||
|
"### $subject\n$newBody"
|
||||||
|
} else {
|
||||||
|
newBody
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(text = eventContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,6 +77,21 @@ fun RefresheableFeedView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DVMStatusView(
|
||||||
|
viewModel: FeedViewModel,
|
||||||
|
routeForLastRead: String?,
|
||||||
|
enablePullRefresh: Boolean = true,
|
||||||
|
scrollStateKey: String? = null,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
viewModel.invalidateData()
|
||||||
|
SaveableFeedState(viewModel, scrollStateKey) { listState ->
|
||||||
|
RenderFeedState(viewModel, accountViewModel, listState, nav, routeForLastRead)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RefresheableBox(
|
fun RefresheableBox(
|
||||||
viewModel: InvalidatableViewModel,
|
viewModel: InvalidatableViewModel,
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter
|
|||||||
import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.NIP90ContentDiscoveryFilter
|
import com.vitorpamplona.amethyst.ui.dal.NIP90ContentDiscoveryFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.NIP90StatusFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileAppRecommendationsFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.UserProfileAppRecommendationsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileBookmarksFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.UserProfileBookmarksFeedFilter
|
||||||
@@ -283,12 +284,22 @@ class NostrBookmarkPrivateFeedViewModel(val account: Account) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class NostrNIP90ContentDiscoveryFeedViewModel(val account: Account, val dvmkey: String) :
|
class NostrNIP90ContentDiscoveryFeedViewModel(val account: Account, val dvmkey: String, val requestid: String) :
|
||||||
// FeedViewModel(BookmarkPrivateFeedFilter(account)) {
|
FeedViewModel(NIP90ContentDiscoveryFilter(account, dvmkey, requestid)) {
|
||||||
FeedViewModel(NIP90ContentDiscoveryFilter(account, dvmkey)) {
|
class Factory(val account: Account, val dvmkey: String, val requestid: String) : ViewModelProvider.Factory {
|
||||||
class Factory(val account: Account, val dvmkey: String) : ViewModelProvider.Factory {
|
|
||||||
override fun <NostrNIP90ContentDiscoveryFeedViewModel : ViewModel> create(modelClass: Class<NostrNIP90ContentDiscoveryFeedViewModel>): NostrNIP90ContentDiscoveryFeedViewModel {
|
override fun <NostrNIP90ContentDiscoveryFeedViewModel : ViewModel> create(modelClass: Class<NostrNIP90ContentDiscoveryFeedViewModel>): NostrNIP90ContentDiscoveryFeedViewModel {
|
||||||
return NostrNIP90ContentDiscoveryFeedViewModel(account, dvmkey) as NostrNIP90ContentDiscoveryFeedViewModel
|
println("FILTERREQUEST " + requestid)
|
||||||
|
return NostrNIP90ContentDiscoveryFeedViewModel(account, dvmkey, requestid) as NostrNIP90ContentDiscoveryFeedViewModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class NostrNIP90StatusFeedViewModel(val account: Account, val dvmkey: String, val requestid: String) :
|
||||||
|
FeedViewModel(NIP90StatusFilter(account, dvmkey, requestid)) {
|
||||||
|
class Factory(val account: Account, val dvmkey: String, val requestid: String) : ViewModelProvider.Factory {
|
||||||
|
override fun <NostrNIP90StatusFeedViewModel : ViewModel> create(modelClass: Class<NostrNIP90StatusFeedViewModel>): NostrNIP90StatusFeedViewModel {
|
||||||
|
return NostrNIP90StatusFeedViewModel(account, dvmkey, requestid) as NostrNIP90StatusFeedViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-17
@@ -34,7 +34,9 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.service.relays.Client
|
import com.vitorpamplona.amethyst.service.relays.Client
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.DVMStatusView
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrNIP90ContentDiscoveryFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrNIP90ContentDiscoveryFeedViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.NostrNIP90StatusFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView
|
import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView
|
||||||
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryRequestEvent
|
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryRequestEvent
|
||||||
|
|
||||||
@@ -44,10 +46,34 @@ fun NIP90ContentDiscoveryScreen(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
var requestID = ""
|
||||||
|
val thread =
|
||||||
|
Thread {
|
||||||
|
try {
|
||||||
|
NIP90ContentDiscoveryRequestEvent.create(DVMID, accountViewModel.account.signer) {
|
||||||
|
Client.send(it)
|
||||||
|
requestID = it.id
|
||||||
|
println("REQUESTID: " + requestID)
|
||||||
|
LocalCache.justConsume(it, null)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thread.start()
|
||||||
|
thread.join()
|
||||||
|
|
||||||
val resultFeedViewModel: NostrNIP90ContentDiscoveryFeedViewModel =
|
val resultFeedViewModel: NostrNIP90ContentDiscoveryFeedViewModel =
|
||||||
viewModel(
|
viewModel(
|
||||||
key = "NostrNIP90ContentDiscoveryFeedViewModel",
|
key = "NostrNIP90ContentDiscoveryFeedViewModel",
|
||||||
factory = NostrNIP90ContentDiscoveryFeedViewModel.Factory(accountViewModel.account, dvmkey = DVMID),
|
factory = NostrNIP90ContentDiscoveryFeedViewModel.Factory(accountViewModel.account, dvmkey = DVMID, requestid = requestID),
|
||||||
|
)
|
||||||
|
|
||||||
|
val statusFeedViewModel: NostrNIP90StatusFeedViewModel =
|
||||||
|
viewModel(
|
||||||
|
key = "NostrNIP90StatusFeedViewModel",
|
||||||
|
factory = NostrNIP90StatusFeedViewModel.Factory(accountViewModel.account, dvmkey = DVMID, requestid = requestID),
|
||||||
)
|
)
|
||||||
|
|
||||||
val userState by accountViewModel.account.decryptBookmarks.observeAsState() // TODO
|
val userState by accountViewModel.account.decryptBookmarks.observeAsState() // TODO
|
||||||
@@ -56,7 +82,7 @@ fun NIP90ContentDiscoveryScreen(
|
|||||||
resultFeedViewModel.invalidateData()
|
resultFeedViewModel.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderNostrNIP90ContentDiscoveryScreen(DVMID, accountViewModel, nav, resultFeedViewModel)
|
RenderNostrNIP90ContentDiscoveryScreen(DVMID, accountViewModel, nav, resultFeedViewModel, statusFeedViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -66,11 +92,11 @@ private fun RenderNostrNIP90ContentDiscoveryScreen(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
resultFeedViewModel: NostrNIP90ContentDiscoveryFeedViewModel,
|
resultFeedViewModel: NostrNIP90ContentDiscoveryFeedViewModel,
|
||||||
|
statusFeedViewModel: NostrNIP90StatusFeedViewModel,
|
||||||
) {
|
) {
|
||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
val pagerState = rememberPagerState { 2 }
|
val pagerState = rememberPagerState { 2 }
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
// TODO Render a nice header with image and DVM name from the id
|
// TODO Render a nice header with image and DVM name from the id
|
||||||
|
|
||||||
/* if (DVMID != null) {
|
/* if (DVMID != null) {
|
||||||
@@ -88,26 +114,22 @@ private fun RenderNostrNIP90ContentDiscoveryScreen(
|
|||||||
}
|
}
|
||||||
} */
|
} */
|
||||||
|
|
||||||
if (DVMID != null) {
|
// TODO only show this when the feed below hasnt loaded. I this possible?
|
||||||
val thread =
|
// TODO render this more as a status label rather than a note
|
||||||
Thread {
|
|
||||||
try {
|
|
||||||
NIP90ContentDiscoveryRequestEvent.create(DVMID, accountViewModel.account.signer) {
|
|
||||||
Client.send(it)
|
|
||||||
LocalCache.justConsume(it, null)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
thread.start()
|
DVMStatusView(
|
||||||
}
|
statusFeedViewModel,
|
||||||
|
null,
|
||||||
|
enablePullRefresh = false,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
|
||||||
HorizontalPager(state = pagerState) {
|
HorizontalPager(state = pagerState) {
|
||||||
RefresheableFeedView(
|
RefresheableFeedView(
|
||||||
resultFeedViewModel,
|
resultFeedViewModel,
|
||||||
null,
|
null,
|
||||||
|
enablePullRefresh = false,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class NIP90StatusEvent(
|
|||||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||||
companion object {
|
companion object {
|
||||||
const val KIND = 7000
|
const val KIND = 7000
|
||||||
const val ALT = "NIP90 Content Discovery reply"
|
const val ALT = "NIP90 Status update"
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
|
|||||||
Reference in New Issue
Block a user