diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 880dafacc..696ca2bcf 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -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) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDiscoveryDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDiscoveryDataSource.kt index 01018a695..ab0d301a8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDiscoveryDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDiscoveryDataSource.kt @@ -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 { + 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( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt index 306372b6b..cf08abb1c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/DiscoverNIP89FeedFilter.kt @@ -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() { + 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) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NIP90ContentDiscoveryFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NIP90ContentDiscoveryFilter.kt index 8e60b3d26..b4a483f18 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NIP90ContentDiscoveryFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NIP90ContentDiscoveryFilter.kt @@ -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() { 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 = 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): List { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NIP90StatusFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NIP90StatusFilter.kt new file mode 100644 index 000000000..48330e47f --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NIP90StatusFilter.kt @@ -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() { + 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 { + 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): Set { + 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): Set { + // 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): List { + return collection.toList() // collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index ae128173b..472a69946 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -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, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/NIP90Status.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/NIP90Status.kt new file mode 100644 index 000000000..0c3662332 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/types/NIP90Status.kt @@ -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, + editState: State>, + 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) + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt index a71951120..7304285ad 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt @@ -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, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt index b1ab4d25b..6bff6d6a3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt @@ -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 create(modelClass: Class): 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 create(modelClass: Class): NostrNIP90StatusFeedViewModel { + return NostrNIP90StatusFeedViewModel(account, dvmkey, requestid) as NostrNIP90StatusFeedViewModel } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt index bbc676327..53eefdae3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt @@ -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, ) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP90StatusEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP90StatusEvent.kt index 2445aa41d..ad34650f1 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP90StatusEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/NIP90StatusEvent.kt @@ -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,