add NIP90 status events, update NIP90 feeds based on request id

This commit is contained in:
Believethehype 2024-05-15 12:06:12 +02:00
parent f09b00ff01
commit 142aca40ce
11 changed files with 366 additions and 36 deletions

View File

@ -91,6 +91,7 @@ import com.vitorpamplona.quartz.events.MetadataEvent
import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryRequestEvent
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent
import com.vitorpamplona.quartz.events.NIP90StatusEvent
import com.vitorpamplona.quartz.events.NIP90UserDiscoveryRequestEvent
import com.vitorpamplona.quartz.events.NIP90UserDiscoveryResponseEvent
import com.vitorpamplona.quartz.events.NNSEvent
@ -444,6 +445,34 @@ object LocalCache {
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(
event: NIP90UserDiscoveryResponseEvent,
relay: Relay? = null,
@ -2415,6 +2444,7 @@ object LocalCache {
}
}
is LnZapRequestEvent -> consume(event)
is NIP90StatusEvent -> consume(event, relay)
is NIP90ContentDiscoveryResponseEvent -> consume(event, relay)
is NIP90ContentDiscoveryRequestEvent -> consume(event, relay)
is NIP90UserDiscoveryResponseEvent -> consume(event, relay)

View File

@ -159,7 +159,25 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") {
types = setOf(FeedType.GLOBAL),
filter =
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,
since =
latestEOSEs.users[account.userProfile()]
@ -446,6 +464,7 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") {
createLiveStreamFilter()
.plus(createNIP89Filter(listOf("5300")))
.plus(createNIP90ResponseFilter())
.plus(createNIP90StatusFilter())
.plus(createPublicChatFilter())
.plus(createMarketplaceFilter())
.plus(

View File

@ -26,10 +26,14 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.events.AppDefinitionEvent
import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.utils.TimeUtils
open class DiscoverNIP89FeedFilter(
val account: Account,
) : 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 {
return account.userProfile().pubkeyHex + "-" + followList()
}
@ -49,7 +53,7 @@ open class DiscoverNIP89FeedFilter(
val notes =
LocalCache.addressables.filterIntoSet { _, it ->
val noteEvent = it.event
noteEvent is AppDefinitionEvent // && params.match(noteEvent)
noteEvent is AppDefinitionEvent && noteEvent.createdAt > TimeUtils.now() - lastAnnounced // && params.match(noteEvent)
}
return sort(notes)
@ -73,7 +77,7 @@ open class DiscoverNIP89FeedFilter(
return collection.filterTo(HashSet()) {
val noteEvent = it.event
noteEvent is AppDefinitionEvent // && params.match(noteEvent)
noteEvent is AppDefinitionEvent && noteEvent.createdAt > TimeUtils.now() - lastAnnounced // && params.match(noteEvent)
}
}

View File

@ -25,7 +25,6 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent
import com.vitorpamplona.quartz.events.PeopleListEvent
@ -33,9 +32,10 @@ import com.vitorpamplona.quartz.events.PeopleListEvent
open class NIP90ContentDiscoveryFilter(
val account: Account,
val dvmkey: String,
val request: String,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + followList()
return account.userProfile().pubkeyHex + "-" + request
}
open fun followList(): String {
@ -53,8 +53,10 @@ open class NIP90ContentDiscoveryFilter(
val notes =
LocalCache.notes.filterIntoSet { _, it ->
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)
if (sorted.isNotEmpty()) {
var note = sorted.first()
@ -99,16 +101,17 @@ open class NIP90ContentDiscoveryFilter(
val notes =
collection.filterTo(HashSet()) {
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
var sorted = sort(notes)
val sorted = sort(notes)
println("REQUEST: " + request)
if (sorted.isNotEmpty()) {
var note = sorted.first()
var eventContent = note.event?.content()
println(eventContent)
val collection: HashSet<Note> = hashSetOf()
val mapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
@ -118,7 +121,7 @@ open class NIP90ContentDiscoveryFilter(
// TODO. This is ugly. how to Kotlin?
var id = element.toString().trimStart('[').trimStart('e').trimStart(',').trimEnd(']').trimStart().trimEnd()
var note = LocalCache.checkGetOrCreateNote(id)
val note = LocalCache.checkGetOrCreateNote(id)
if (note != null) {
collection.add(note)
}
@ -127,8 +130,6 @@ open class NIP90ContentDiscoveryFilter(
} else {
return notes
}
return notes
}
override fun sort(collection: Set<Note>): List<Note> {

View File

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

View File

@ -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.RenderLongFormContent
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.RenderPoll
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.LongTextNoteEvent
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent
import com.vitorpamplona.quartz.events.NIP90StatusEvent
import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.events.PinListEvent
import com.vitorpamplona.quartz.events.PollNoteEvent
@ -683,6 +685,19 @@ private fun RenderNoteRow(
nav,
)
is NIP90StatusEvent ->
RenderNIP90Status(
baseNote,
makeItShort,
canPreview,
quotesLeft,
unPackReply,
backgroundColor,
editState,
accountViewModel,
nav,
)
is PollNoteEvent -> {
RenderPoll(
baseNote,

View File

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

View File

@ -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
fun RefresheableBox(
viewModel: InvalidatableViewModel,

View File

@ -55,6 +55,7 @@ import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter
import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
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.UserProfileAppRecommendationsFeedFilter
import com.vitorpamplona.amethyst.ui.dal.UserProfileBookmarksFeedFilter
@ -283,12 +284,22 @@ class NostrBookmarkPrivateFeedViewModel(val account: Account) :
}
@Stable
class NostrNIP90ContentDiscoveryFeedViewModel(val account: Account, val dvmkey: String) :
// FeedViewModel(BookmarkPrivateFeedFilter(account)) {
FeedViewModel(NIP90ContentDiscoveryFilter(account, dvmkey)) {
class Factory(val account: Account, val dvmkey: String) : ViewModelProvider.Factory {
class NostrNIP90ContentDiscoveryFeedViewModel(val account: Account, val dvmkey: String, val requestid: String) :
FeedViewModel(NIP90ContentDiscoveryFilter(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 {
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
}
}
}

View File

@ -34,7 +34,9 @@ import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.model.LocalCache
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.NostrNIP90StatusFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryRequestEvent
@ -44,10 +46,34 @@ fun NIP90ContentDiscoveryScreen(
accountViewModel: AccountViewModel,
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 =
viewModel(
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
@ -56,7 +82,7 @@ fun NIP90ContentDiscoveryScreen(
resultFeedViewModel.invalidateData()
}
RenderNostrNIP90ContentDiscoveryScreen(DVMID, accountViewModel, nav, resultFeedViewModel)
RenderNostrNIP90ContentDiscoveryScreen(DVMID, accountViewModel, nav, resultFeedViewModel, statusFeedViewModel)
}
@Composable
@ -66,11 +92,11 @@ private fun RenderNostrNIP90ContentDiscoveryScreen(
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
resultFeedViewModel: NostrNIP90ContentDiscoveryFeedViewModel,
statusFeedViewModel: NostrNIP90StatusFeedViewModel,
) {
Column(Modifier.fillMaxHeight()) {
val pagerState = rememberPagerState { 2 }
val coroutineScope = rememberCoroutineScope()
// TODO Render a nice header with image and DVM name from the id
/* if (DVMID != null) {
@ -88,26 +114,22 @@ private fun RenderNostrNIP90ContentDiscoveryScreen(
}
} */
if (DVMID != null) {
val thread =
Thread {
try {
NIP90ContentDiscoveryRequestEvent.create(DVMID, accountViewModel.account.signer) {
Client.send(it)
LocalCache.justConsume(it, null)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
// TODO only show this when the feed below hasnt loaded. I this possible?
// TODO render this more as a status label rather than a note
thread.start()
}
DVMStatusView(
statusFeedViewModel,
null,
enablePullRefresh = false,
accountViewModel = accountViewModel,
nav = nav,
)
HorizontalPager(state = pagerState) {
RefresheableFeedView(
resultFeedViewModel,
null,
enablePullRefresh = false,
accountViewModel = accountViewModel,
nav = nav,
)

View File

@ -36,7 +36,7 @@ class NIP90StatusEvent(
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
companion object {
const val KIND = 7000
const val ALT = "NIP90 Content Discovery reply"
const val ALT = "NIP90 Status update"
fun create(
signer: NostrSigner,