mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-03-26 17:52:29 +01:00
Merge branch 'main' of https://github.com/vitorpamplona/amethyst
This commit is contained in:
commit
d9de0d2798
@ -89,6 +89,11 @@ import com.vitorpamplona.quartz.events.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.events.LongTextNoteEvent
|
||||
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
|
||||
import com.vitorpamplona.quartz.events.OtsEvent
|
||||
import com.vitorpamplona.quartz.events.PeopleListEvent
|
||||
@ -384,6 +389,146 @@ object LocalCache {
|
||||
refreshObservers(note)
|
||||
}
|
||||
|
||||
fun consume(
|
||||
event: NIP90ContentDiscoveryResponseEvent,
|
||||
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: NIP90ContentDiscoveryRequestEvent,
|
||||
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: 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,
|
||||
) {
|
||||
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: NIP90UserDiscoveryRequestEvent,
|
||||
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: GitPatchEvent,
|
||||
relay: Relay? = null,
|
||||
@ -2299,6 +2444,11 @@ 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)
|
||||
is NIP90UserDiscoveryRequestEvent -> consume(event, relay)
|
||||
is LnZapPaymentRequestEvent -> consume(event)
|
||||
is LnZapPaymentResponseEvent -> consume(event)
|
||||
is LongTextNoteEvent -> consume(event, relay)
|
||||
|
@ -26,6 +26,7 @@ import com.vitorpamplona.amethyst.service.relays.EOSEAccount
|
||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||
import com.vitorpamplona.amethyst.service.relays.JsonFilter
|
||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||
import com.vitorpamplona.quartz.events.AppDefinitionEvent
|
||||
import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.events.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.events.ChannelMetadataEvent
|
||||
@ -34,6 +35,8 @@ import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent
|
||||
import com.vitorpamplona.quartz.events.NIP90StatusEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
@ -131,6 +134,61 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") {
|
||||
)
|
||||
}
|
||||
|
||||
fun createNIP89Filter(kTags: List<String>): List<TypedFilter> {
|
||||
return listOfNotNull(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.GLOBAL),
|
||||
filter =
|
||||
JsonFilter(
|
||||
kinds = listOf(AppDefinitionEvent.KIND),
|
||||
limit = 300,
|
||||
tags = mapOf("k" to kTags),
|
||||
since =
|
||||
latestEOSEs.users[account.userProfile()]
|
||||
?.followList
|
||||
?.get(account.defaultDiscoveryFollowList.value)
|
||||
?.relayList,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun createNIP90ResponseFilter(): List<TypedFilter> {
|
||||
return listOfNotNull(
|
||||
TypedFilter(
|
||||
types = setOf(FeedType.GLOBAL),
|
||||
filter =
|
||||
JsonFilter(
|
||||
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()]
|
||||
?.followList
|
||||
?.get(account.defaultDiscoveryFollowList.value)
|
||||
?.relayList,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun createLiveStreamFilter(): List<TypedFilter> {
|
||||
val follows = account.liveDiscoveryFollowLists.value?.users?.toList()
|
||||
|
||||
@ -404,6 +462,9 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") {
|
||||
override fun updateChannelFilters() {
|
||||
discoveryFeedChannel.typedFilters =
|
||||
createLiveStreamFilter()
|
||||
.plus(createNIP89Filter(listOf("5300")))
|
||||
.plus(createNIP90ResponseFilter())
|
||||
.plus(createNIP90StatusFilter())
|
||||
.plus(createPublicChatFilter())
|
||||
.plus(createMarketplaceFilter())
|
||||
.plus(
|
||||
@ -417,6 +478,7 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") {
|
||||
createPublicChatsGeohashesFilter(),
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
.ifEmpty { null }
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 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.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()
|
||||
}
|
||||
|
||||
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 notes =
|
||||
LocalCache.addressables.filterIntoSet { _, it ->
|
||||
val noteEvent = it.event
|
||||
noteEvent is AppDefinitionEvent && noteEvent.createdAt > TimeUtils.now() - lastAnnounced // && params.match(noteEvent)
|
||||
}
|
||||
|
||||
return sort(notes)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
return collection.filterTo(HashSet()) {
|
||||
val noteEvent = it.event
|
||||
noteEvent is AppDefinitionEvent && noteEvent.createdAt > TimeUtils.now() - lastAnnounced // && params.match(noteEvent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun sort(collection: Set<Note>): List<Note> {
|
||||
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
/**
|
||||
* 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.fasterxml.jackson.databind.DeserializationFeature
|
||||
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.events.MuteListEvent
|
||||
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent
|
||||
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 + "-" + request
|
||||
}
|
||||
|
||||
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 notes =
|
||||
LocalCache.notes.filterIntoSet { _, it ->
|
||||
val noteEvent = it.event
|
||||
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()
|
||||
|
||||
var eventContent = note.event?.content()
|
||||
|
||||
var collection: MutableSet<Note> = mutableSetOf()
|
||||
val mapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
var etags = mapper.readValue(eventContent, List::class.java)
|
||||
for (element in etags) {
|
||||
var tag = mapper.readValue(mapper.writeValueAsString(element), Array::class.java)
|
||||
val note = LocalCache.checkGetOrCreateNote(tag[1].toString())
|
||||
if (note != null) {
|
||||
collection.add(note)
|
||||
}
|
||||
}
|
||||
|
||||
return collection.toList()
|
||||
} else {
|
||||
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 notes =
|
||||
collection.filterTo(HashSet()) {
|
||||
val noteEvent = it.event
|
||||
noteEvent is NIP90ContentDiscoveryResponseEvent && // &&
|
||||
it.event!!.isTaggedEvent(request) // && it.event?.isTaggedUser(account.keyPair.pubKey.toHexKey()) == true // && params.match(noteEvent)
|
||||
}
|
||||
|
||||
val sorted = sort(notes)
|
||||
if (sorted.isNotEmpty()) {
|
||||
var note = sorted.first()
|
||||
|
||||
var eventContent = note.event?.content()
|
||||
// println(eventContent)
|
||||
|
||||
val collection: MutableSet<Note> = mutableSetOf()
|
||||
val mapper = jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
var etags = mapper.readValue(eventContent, Array::class.java)
|
||||
for (element in etags) {
|
||||
var tag = mapper.readValue(mapper.writeValueAsString(element), Array::class.java)
|
||||
val note = LocalCache.checkGetOrCreateNote(tag[1].toString())
|
||||
if (note != null) {
|
||||
collection.add(note)
|
||||
}
|
||||
}
|
||||
return collection
|
||||
} else {
|
||||
return hashSetOf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun sort(collection: Set<Note>): List<Note> {
|
||||
return collection.toList() // collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
/**
|
||||
* 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()) {
|
||||
return listOf(status.first())
|
||||
} else {
|
||||
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()) {
|
||||
return setOf(status.first())
|
||||
} else {
|
||||
return setOf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun sort(collection: Set<Note>): List<Note> {
|
||||
return collection.toList() // collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
|
||||
}
|
||||
}
|
@ -37,6 +37,9 @@ import com.vitorpamplona.quartz.events.HighlightEvent
|
||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||
import com.vitorpamplona.quartz.events.LnZapRequestEvent
|
||||
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.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.events.ReactionEvent
|
||||
import com.vitorpamplona.quartz.events.RepostEvent
|
||||
@ -111,6 +114,7 @@ class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
|
||||
it.event !is LnZapRequestEvent &&
|
||||
it.event !is BadgeDefinitionEvent &&
|
||||
it.event !is BadgeProfilesEvent &&
|
||||
it.event !is NIP90ContentDiscoveryResponseEvent && it.event !is NIP90StatusEvent && it.event !is NIP90ContentDiscoveryRequestEvent &&
|
||||
it.event !is GiftWrapEvent &&
|
||||
(it.event is LnZapEvent || notifAuthor != loggedInUserHex) &&
|
||||
(filterParams.isGlobal || filterParams.followLists?.users?.contains(notifAuthor) == true) &&
|
||||
|
@ -48,6 +48,7 @@ import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverChatFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverCommunityFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverLiveFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverMarketplaceFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverNIP89FeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
|
||||
@ -67,6 +68,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.HashtagScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.HiddenUsersScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.HomeScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LoadRedirectScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.NIP90ContentDiscoveryScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.NotificationScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ProfileScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchScreen
|
||||
@ -86,6 +88,7 @@ fun AppNavigation(
|
||||
newFeedViewModel: NostrChatroomListNewFeedViewModel,
|
||||
videoFeedViewModel: NostrVideoFeedViewModel,
|
||||
discoverMarketplaceFeedViewModel: NostrDiscoverMarketplaceFeedViewModel,
|
||||
discoverNip89FeedViewModel: NostrDiscoverNIP89FeedViewModel,
|
||||
discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel,
|
||||
discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel,
|
||||
discoveryChatFeedViewModel: NostrDiscoverChatFeedViewModel,
|
||||
@ -173,6 +176,7 @@ fun AppNavigation(
|
||||
route.arguments,
|
||||
content = {
|
||||
DiscoverScreen(
|
||||
discoveryContentNIP89FeedViewModel = discoverNip89FeedViewModel,
|
||||
discoveryMarketplaceFeedViewModel = discoverMarketplaceFeedViewModel,
|
||||
discoveryLiveFeedViewModel = discoveryLiveFeedViewModel,
|
||||
discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel,
|
||||
@ -215,8 +219,25 @@ fun AppNavigation(
|
||||
|
||||
composable(Route.BlockedUsers.route, content = { HiddenUsersScreen(accountViewModel, nav) })
|
||||
composable(Route.Bookmarks.route, content = { BookmarkListScreen(accountViewModel, nav) })
|
||||
|
||||
composable(Route.Drafts.route, content = { DraftListScreen(accountViewModel, nav) })
|
||||
|
||||
Route.ContentDiscovery.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
route.arguments,
|
||||
content = {
|
||||
it.arguments?.getString("id")?.let { it1 ->
|
||||
NIP90ContentDiscoveryScreen(
|
||||
DVMID = it1,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Route.Profile.let { route ->
|
||||
composable(
|
||||
route.route,
|
||||
|
@ -188,6 +188,8 @@ private fun RenderTopRouteBar(
|
||||
Route.Settings.base -> TopBarWithBackButton(stringResource(id = R.string.application_preferences), navPopBack)
|
||||
Route.Bookmarks.base -> TopBarWithBackButton(stringResource(id = R.string.bookmarks), navPopBack)
|
||||
Route.Drafts.base -> TopBarWithBackButton(stringResource(id = R.string.drafts), navPopBack)
|
||||
Route.ContentDiscovery.base -> TopBarWithBackButton(stringResource(id = R.string.discover_content), navPopBack)
|
||||
|
||||
else -> {
|
||||
if (id != null) {
|
||||
when (currentRoute) {
|
||||
|
@ -148,6 +148,14 @@ sealed class Route(
|
||||
contentDescriptor = R.string.route_home,
|
||||
)
|
||||
|
||||
object ContentDiscovery :
|
||||
Route(
|
||||
icon = R.drawable.ic_bookmarks,
|
||||
contentDescriptor = R.string.discover_content,
|
||||
route = "ContentDiscovery/{id}",
|
||||
arguments = listOf(navArgument("id") { type = NavType.StringType }).toImmutableList(),
|
||||
)
|
||||
|
||||
object Drafts :
|
||||
Route(
|
||||
route = "Drafts",
|
||||
|
@ -89,6 +89,7 @@ import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.events.AppDefinitionEvent
|
||||
import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.events.ClassifiedsEvent
|
||||
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
||||
@ -213,6 +214,9 @@ fun InnerChannelCardWithReactions(
|
||||
is ClassifiedsEvent -> {
|
||||
InnerCardBox(baseNote, accountViewModel, nav)
|
||||
}
|
||||
is AppDefinitionEvent -> {
|
||||
InnerCardRow(baseNote, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,6 +272,9 @@ private fun RenderNoteRow(
|
||||
is ChannelCreateEvent -> {
|
||||
RenderChannelThumb(baseNote, accountViewModel, nav)
|
||||
}
|
||||
is AppDefinitionEvent -> {
|
||||
RenderContentDVMThumb(baseNote, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -516,6 +523,13 @@ data class CommunityCard(
|
||||
val moderators: ImmutableList<Participant>,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class DVMCard(
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val cover: String?,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderCommunitiesThumb(
|
||||
baseNote: Note,
|
||||
@ -715,6 +729,92 @@ private fun LoadParticipants(
|
||||
inner(participantUsers)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderContentDVMThumb(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
val noteEvent = baseNote.event as? AppDefinitionEvent ?: return
|
||||
|
||||
val card by
|
||||
baseNote
|
||||
.live()
|
||||
.metadata
|
||||
.map {
|
||||
val noteEvent = it.note.event as? AppDefinitionEvent
|
||||
|
||||
DVMCard(
|
||||
name = noteEvent?.appMetaData()?.name ?: "",
|
||||
description = noteEvent?.appMetaData()?.about ?: "",
|
||||
cover = noteEvent?.appMetaData()?.image?.ifBlank { null },
|
||||
)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.observeAsState(
|
||||
DVMCard(
|
||||
name = noteEvent.appMetaData()?.name ?: "",
|
||||
description = noteEvent.appMetaData()?.about ?: "",
|
||||
cover = noteEvent.appMetaData()?.image?.ifBlank { null },
|
||||
),
|
||||
)
|
||||
|
||||
LeftPictureLayout(
|
||||
onImage = {
|
||||
card.cover?.let {
|
||||
Box(contentAlignment = BottomStart) {
|
||||
AsyncImage(
|
||||
model = it,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.fillMaxSize().clip(QuoteBorder),
|
||||
)
|
||||
}
|
||||
} ?: run { DisplayAuthorBanner(baseNote) }
|
||||
},
|
||||
onTitleRow = {
|
||||
Text(
|
||||
text = card.name,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
LikeReaction(
|
||||
baseNote = baseNote,
|
||||
grayTint = MaterialTheme.colorScheme.onSurface,
|
||||
accountViewModel = accountViewModel,
|
||||
nav,
|
||||
)
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
ZapReaction(
|
||||
baseNote = baseNote,
|
||||
grayTint = MaterialTheme.colorScheme.onSurface,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
},
|
||||
onDescription = {
|
||||
card.description?.let {
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
Row {
|
||||
Text(
|
||||
text = it,
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onBottomRow = {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RenderChannelThumb(
|
||||
baseNote: Note,
|
||||
|
@ -99,6 +99,8 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderHighlight
|
||||
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
|
||||
@ -161,6 +163,8 @@ import com.vitorpamplona.quartz.events.HighlightEvent
|
||||
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
|
||||
@ -420,13 +424,18 @@ fun ClickableNote(
|
||||
.combinedClickable(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
val redirectToNote =
|
||||
if (baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent) {
|
||||
baseNote.replyTo?.lastOrNull() ?: baseNote
|
||||
} else {
|
||||
baseNote
|
||||
}
|
||||
routeFor(redirectToNote, accountViewModel.userProfile())?.let { nav(it) }
|
||||
if (baseNote.event is AppDefinitionEvent) {
|
||||
// nav(Route.ContentDiscovery.route + "/${(baseNote.event as AppDefinitionEvent).pubKey()}")
|
||||
nav("ContentDiscovery/${(baseNote.event as AppDefinitionEvent).pubKey()}")
|
||||
} else {
|
||||
val redirectToNote =
|
||||
if (baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent) {
|
||||
baseNote.replyTo?.lastOrNull() ?: baseNote
|
||||
} else {
|
||||
baseNote
|
||||
}
|
||||
routeFor(redirectToNote, accountViewModel.userProfile())?.let { nav(it) }
|
||||
}
|
||||
}
|
||||
},
|
||||
onLongClick = showPopup,
|
||||
@ -663,6 +672,32 @@ private fun RenderNoteRow(
|
||||
nav,
|
||||
)
|
||||
}
|
||||
is NIP90ContentDiscoveryResponseEvent ->
|
||||
RenderNIP90ContentDiscoveryResponse(
|
||||
baseNote,
|
||||
makeItShort,
|
||||
canPreview,
|
||||
quotesLeft,
|
||||
unPackReply,
|
||||
backgroundColor,
|
||||
editState,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
|
||||
is NIP90StatusEvent ->
|
||||
RenderNIP90Status(
|
||||
baseNote,
|
||||
makeItShort,
|
||||
canPreview,
|
||||
quotesLeft,
|
||||
unPackReply,
|
||||
backgroundColor,
|
||||
editState,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
|
||||
is PollNoteEvent -> {
|
||||
RenderPoll(
|
||||
baseNote,
|
||||
|
@ -70,8 +70,8 @@ import com.vitorpamplona.amethyst.ui.theme.Size16Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.events.AppDefinitionEvent
|
||||
import com.vitorpamplona.quartz.events.AppMetadata
|
||||
import com.vitorpamplona.quartz.events.EmptyTagList
|
||||
import com.vitorpamplona.quartz.events.UserMetadata
|
||||
import com.vitorpamplona.quartz.events.toImmutableListOfLists
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -85,7 +85,7 @@ fun RenderAppDefinition(
|
||||
) {
|
||||
val noteEvent = note.event as? AppDefinitionEvent ?: return
|
||||
|
||||
var metadata by remember { mutableStateOf<UserMetadata?>(null) }
|
||||
var metadata by remember { mutableStateOf<AppMetadata?>(null) }
|
||||
|
||||
LaunchedEffect(key1 = noteEvent) {
|
||||
withContext(Dispatchers.Default) { metadata = noteEvent.appMetaData() }
|
||||
|
@ -0,0 +1,154 @@
|
||||
/**
|
||||
* 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.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
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.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadDecryptedContent
|
||||
import com.vitorpamplona.amethyst.ui.note.ReplyNoteComposition
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.quartz.events.BaseTextNoteEvent
|
||||
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.events.EmptyTagList
|
||||
import com.vitorpamplona.quartz.events.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.events.toImmutableListOfLists
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Composable
|
||||
fun RenderNIP90ContentDiscoveryResponse(
|
||||
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 }
|
||||
}
|
||||
}
|
||||
if (replyingDirectlyTo != null && unPackReply) {
|
||||
ReplyNoteComposition(replyingDirectlyTo, backgroundColor, accountViewModel, nav)
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val isAuthorTheLoggedUser =
|
||||
remember(note.event) { accountViewModel.isLoggedUser(note.author) }
|
||||
|
||||
SensitivityWarning(
|
||||
note = note,
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
val modifier = remember(note) { Modifier.fillMaxWidth() }
|
||||
val tags =
|
||||
remember(note) { note.event?.tags()?.toImmutableListOfLists() ?: EmptyTagList }
|
||||
|
||||
TranslatableRichTextViewer(
|
||||
content = eventContent,
|
||||
canPreview = canPreview && !makeItShort,
|
||||
quotesLeft = quotesLeft,
|
||||
modifier = modifier,
|
||||
tags = tags,
|
||||
backgroundColor = backgroundColor,
|
||||
id = note.idHex,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
|
||||
if (note.event?.hasHashtags() == true) {
|
||||
val hashtags =
|
||||
remember(note.event) {
|
||||
note.event?.hashtags()?.toImmutableList() ?: persistentListOf()
|
||||
}
|
||||
DisplayUncitedHashtags(hashtags, eventContent, nav)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
fun RefresheableBox(
|
||||
viewModel: InvalidatableViewModel,
|
||||
@ -286,3 +301,19 @@ fun FeedEmpty(onRefresh: () -> Unit) {
|
||||
OutlinedButton(onClick = onRefresh) { Text(text = stringResource(R.string.refresh)) }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FeedEmptywithStatus(
|
||||
status: String,
|
||||
onRefresh: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(status)
|
||||
// Spacer(modifier = StdVertSpacer)
|
||||
// OutlinedButton(onClick = onRefresh) { Text(text = stringResource(R.string.refresh)) }
|
||||
}
|
||||
}
|
||||
|
@ -47,12 +47,15 @@ import com.vitorpamplona.amethyst.ui.dal.DiscoverChatFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.DiscoverCommunityFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.DiscoverLiveFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.DiscoverMarketplaceFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.DiscoverNIP89FeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.DraftEventsFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.GeoHashFeedFilter
|
||||
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
|
||||
@ -109,6 +112,17 @@ class NostrDiscoverMarketplaceFeedViewModel(val account: Account) :
|
||||
}
|
||||
}
|
||||
|
||||
class NostrDiscoverNIP89FeedViewModel(val account: Account) :
|
||||
FeedViewModel(
|
||||
DiscoverNIP89FeedFilter(account),
|
||||
) {
|
||||
class Factory(val account: Account) : ViewModelProvider.Factory {
|
||||
override fun <NostrDiscoverNIP89FeedViewModel : ViewModel> create(modelClass: Class<NostrDiscoverNIP89FeedViewModel>): NostrDiscoverNIP89FeedViewModel {
|
||||
return NostrDiscoverNIP89FeedViewModel(account) as NostrDiscoverNIP89FeedViewModel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NostrDiscoverLiveFeedViewModel(val account: Account) :
|
||||
FeedViewModel(DiscoverLiveFeedFilter(account)) {
|
||||
class Factory(val account: Account) : ViewModelProvider.Factory {
|
||||
@ -269,6 +283,26 @@ class NostrBookmarkPrivateFeedViewModel(val account: Account) :
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
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, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
class NostrDraftEventsFeedViewModel(val account: Account) :
|
||||
FeedViewModel(DraftEventsFeedFilter(account)) {
|
||||
|
@ -43,6 +43,7 @@ object ScrollStateKeys {
|
||||
val HOME_FOLLOWS = Route.Home.base + "Follows"
|
||||
val HOME_REPLIES = Route.Home.base + "FollowsReplies"
|
||||
|
||||
val DISCOVER_CONTENT = Route.Home.base + "DiscoverContent"
|
||||
val DISCOVER_MARKETPLACE = Route.Home.base + "Marketplace"
|
||||
val DISCOVER_LIVE = Route.Home.base + "Live"
|
||||
val DISCOVER_COMMUNITY = Route.Home.base + "Communities"
|
||||
|
@ -69,6 +69,7 @@ import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverChatFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverCommunityFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverLiveFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverMarketplaceFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverNIP89FeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.PagerStateKeys
|
||||
import com.vitorpamplona.amethyst.ui.screen.RefresheableBox
|
||||
import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState
|
||||
@ -78,6 +79,7 @@ import com.vitorpamplona.amethyst.ui.screen.rememberForeverPagerState
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
|
||||
import com.vitorpamplona.quartz.events.AppDefinitionEvent
|
||||
import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.events.ClassifiedsEvent
|
||||
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
||||
@ -89,6 +91,7 @@ import kotlinx.coroutines.launch
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun DiscoverScreen(
|
||||
discoveryContentNIP89FeedViewModel: NostrDiscoverNIP89FeedViewModel,
|
||||
discoveryMarketplaceFeedViewModel: NostrDiscoverMarketplaceFeedViewModel,
|
||||
discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel,
|
||||
discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel,
|
||||
@ -100,12 +103,20 @@ fun DiscoverScreen(
|
||||
|
||||
val tabs by
|
||||
remember(
|
||||
discoveryContentNIP89FeedViewModel,
|
||||
discoveryLiveFeedViewModel,
|
||||
discoveryCommunityFeedViewModel,
|
||||
discoveryChatFeedViewModel,
|
||||
) {
|
||||
mutableStateOf(
|
||||
listOf(
|
||||
TabItem(
|
||||
R.string.discover_content,
|
||||
discoveryContentNIP89FeedViewModel,
|
||||
Route.Discover.base + "DiscoverContent",
|
||||
ScrollStateKeys.DISCOVER_CONTENT,
|
||||
AppDefinitionEvent.KIND,
|
||||
),
|
||||
TabItem(
|
||||
R.string.discover_marketplace,
|
||||
discoveryMarketplaceFeedViewModel,
|
||||
@ -142,6 +153,7 @@ fun DiscoverScreen(
|
||||
val pagerState = rememberForeverPagerState(key = PagerStateKeys.DISCOVER_SCREEN) { tabs.size }
|
||||
|
||||
WatchAccountForDiscoveryScreen(
|
||||
discoverNIP89FeedViewModel = discoveryContentNIP89FeedViewModel,
|
||||
discoverMarketplaceFeedViewModel = discoveryMarketplaceFeedViewModel,
|
||||
discoveryLiveFeedViewModel = discoveryLiveFeedViewModel,
|
||||
discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel,
|
||||
@ -310,6 +322,7 @@ private fun RenderDiscoverFeed(
|
||||
|
||||
@Composable
|
||||
fun WatchAccountForDiscoveryScreen(
|
||||
discoverNIP89FeedViewModel: NostrDiscoverNIP89FeedViewModel,
|
||||
discoverMarketplaceFeedViewModel: NostrDiscoverMarketplaceFeedViewModel,
|
||||
discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel,
|
||||
discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel,
|
||||
@ -320,6 +333,7 @@ fun WatchAccountForDiscoveryScreen(
|
||||
|
||||
LaunchedEffect(accountViewModel, listState) {
|
||||
NostrDiscoveryDataSource.resetFilters()
|
||||
discoverNIP89FeedViewModel.checkKeysInvalidateDataAndSendToTop()
|
||||
discoverMarketplaceFeedViewModel.checkKeysInvalidateDataAndSendToTop()
|
||||
discoveryLiveFeedViewModel.checkKeysInvalidateDataAndSendToTop()
|
||||
discoveryCommunityFeedViewModel.checkKeysInvalidateDataAndSendToTop()
|
||||
@ -344,20 +358,30 @@ private fun DiscoverFeedLoaded(
|
||||
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item ->
|
||||
val defaultModifier = remember { Modifier.fillMaxWidth().animateItemPlacement() }
|
||||
|
||||
Row(defaultModifier) {
|
||||
ChannelCardCompose(
|
||||
baseNote = item,
|
||||
routeForLastRead = routeForLastRead,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
forceEventKind = forceEventKind,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
// TODO For now we avoid subscription based DVMs, as we need logic for these first if a user is not subscribed already.
|
||||
var avoid = false
|
||||
if (item.event is AppDefinitionEvent) {
|
||||
if ((item.event as AppDefinitionEvent).appMetaData()?.subscription == true) {
|
||||
avoid = true
|
||||
}
|
||||
}
|
||||
// TODO End
|
||||
if (!avoid) {
|
||||
Row(defaultModifier) {
|
||||
ChannelCardCompose(
|
||||
baseNote = item,
|
||||
routeForLastRead = routeForLastRead,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
forceEventKind = forceEventKind,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverChatFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverCommunityFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverLiveFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverMarketplaceFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverNIP89FeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
|
||||
@ -207,6 +208,12 @@ fun MainScreen(
|
||||
factory = NostrDiscoverMarketplaceFeedViewModel.Factory(accountViewModel.account),
|
||||
)
|
||||
|
||||
val discoverNIP89FeedViewModel: NostrDiscoverNIP89FeedViewModel =
|
||||
viewModel(
|
||||
key = "NostrDiscoveryNIP89FeedViewModel",
|
||||
factory = NostrDiscoverNIP89FeedViewModel.Factory(accountViewModel.account),
|
||||
)
|
||||
|
||||
val discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel =
|
||||
viewModel(
|
||||
key = "NostrDiscoveryLiveFeedViewModel",
|
||||
@ -411,6 +418,7 @@ fun MainScreen(
|
||||
knownFeedViewModel = knownFeedViewModel,
|
||||
newFeedViewModel = newFeedViewModel,
|
||||
videoFeedViewModel = videoFeedViewModel,
|
||||
discoverNip89FeedViewModel = discoverNIP89FeedViewModel,
|
||||
discoverMarketplaceFeedViewModel = discoverMarketplaceFeedViewModel,
|
||||
discoveryLiveFeedViewModel = discoveryLiveFeedViewModel,
|
||||
discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel,
|
||||
|
@ -0,0 +1,156 @@
|
||||
/**
|
||||
* 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.screen.loggedIn
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.relays.Client
|
||||
import com.vitorpamplona.amethyst.ui.screen.FeedEmptywithStatus
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrNIP90ContentDiscoveryFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrNIP90StatusFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.RefresheableBox
|
||||
import com.vitorpamplona.amethyst.ui.screen.RenderFeedState
|
||||
import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState
|
||||
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryRequestEvent
|
||||
|
||||
@Composable
|
||||
fun NIP90ContentDiscoveryScreen(
|
||||
DVMID: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
var requestID = ""
|
||||
val thread =
|
||||
Thread {
|
||||
try {
|
||||
NIP90ContentDiscoveryRequestEvent.create(DVMID, accountViewModel.account.signer) {
|
||||
Client.send(it)
|
||||
requestID = it.id
|
||||
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, 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
|
||||
|
||||
LaunchedEffect(userState) {
|
||||
resultFeedViewModel.invalidateData()
|
||||
}
|
||||
|
||||
RenderNostrNIP90ContentDiscoveryScreen(DVMID, accountViewModel, nav, resultFeedViewModel, statusFeedViewModel)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun RenderNostrNIP90ContentDiscoveryScreen(
|
||||
dvmID: String?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
resultFeedViewModel: NostrNIP90ContentDiscoveryFeedViewModel,
|
||||
statusFeedViewModel: NostrNIP90StatusFeedViewModel,
|
||||
) {
|
||||
Column(Modifier.fillMaxHeight()) {
|
||||
val pagerState = rememberPagerState { 2 }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
// TODO (Optional) this now shows the first status update but there might be a better way
|
||||
var dvmState = stringResource(R.string.dvm_waiting_status)
|
||||
var dvmNoState = stringResource(R.string.dvm_no_status)
|
||||
|
||||
val thread =
|
||||
Thread {
|
||||
var count = 0
|
||||
while (resultFeedViewModel.localFilter.feed().isEmpty()) {
|
||||
try {
|
||||
if (statusFeedViewModel.localFilter.feed().isNotEmpty()) {
|
||||
statusFeedViewModel.localFilter.feed()[0].event?.let { dvmState = it.content() }
|
||||
println(dvmState)
|
||||
break
|
||||
} else if (count > 1000) {
|
||||
dvmState = dvmNoState
|
||||
// Might not be the best way, but we want to avoid hanging in the loop forever
|
||||
} else {
|
||||
count++
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
thread.start()
|
||||
thread.join()
|
||||
|
||||
// TODO (Optional) Maybe render a nice header with image and DVM name from the dvmID
|
||||
// TODO (Optional) How do we get the event information here?, LocalCache.checkGetOrCreateNote() returns note but event is empty
|
||||
// TODO (Optional) otherwise we have the NIP89 info in (note.event as AppDefinitionEvent).appMetaData()
|
||||
// Text(text = dvminfo)
|
||||
|
||||
HorizontalPager(state = pagerState) {
|
||||
RefresheableBox(resultFeedViewModel, false) {
|
||||
SaveableFeedState(resultFeedViewModel, null) { listState ->
|
||||
// TODO (Optional) Instead of a like reaction, do a Kind 31989 NIP89 App recommendation
|
||||
RenderFeedState(
|
||||
resultFeedViewModel,
|
||||
accountViewModel,
|
||||
listState,
|
||||
nav,
|
||||
null,
|
||||
onEmpty = {
|
||||
// TODO (Optional) Maybe also show some dvm image/text while waiting for the notes in this custom component
|
||||
FeedEmptywithStatus(status = dvmState) {
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -54,6 +54,8 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@ -91,6 +93,7 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.events.findHashtags
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
@ -295,6 +298,15 @@ private fun SearchTextField(
|
||||
searchBarViewModel: SearchBarViewModel,
|
||||
onTextChanges: (String) -> Unit,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
launch {
|
||||
delay(100)
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.padding(10.dp).fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
@ -312,7 +324,11 @@ private fun SearchTextField(
|
||||
capitalization = KeyboardCapitalization.Sentences,
|
||||
),
|
||||
leadingIcon = { SearchIcon(modifier = Size20Modifier, Color.Unspecified) },
|
||||
modifier = Modifier.weight(1f, true).defaultMinSize(minHeight = 20.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.weight(1f, true)
|
||||
.defaultMinSize(minHeight = 20.dp)
|
||||
.focusRequester(focusRequester),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.npub_hex_username),
|
||||
|
@ -429,6 +429,7 @@
|
||||
<string name="are_you_sure_you_want_to_log_out">Se déconnecter supprime toutes vos informations locales. Assurez-vous d\'avoir vos clés privées sauvegardées pour éviter de perdre votre compte. Voulez-vous continuer ?</string>
|
||||
<string name="followed_tags">Tags suivis</string>
|
||||
<string name="relay_setup">Relais</string>
|
||||
<string name="discover_content">Découverte de notes</string>
|
||||
<string name="discover_marketplace">Place de Marché</string>
|
||||
<string name="discover_live">Direct</string>
|
||||
<string name="discover_community">Communauté</string>
|
||||
@ -669,6 +670,10 @@
|
||||
<string name="show_npub_as_a_qr_code">Afficher npub en tant que QR code</string>
|
||||
<string name="invalid_nip19_uri">Adresse invalide</string>
|
||||
<string name="invalid_nip19_uri_description">Amethyst a reçu une URI à ouvrir mais cette URI était invalide : %1$s</string>
|
||||
<string name="dm_relays_not_found">Configurer vos relais de messagerie privée</string>
|
||||
<string name="dm_relays_not_found_description">Ce paramètre informe tout le monde les relais à utiliser pour vous envoyer des messages. Sans eux, vous risquez de manquer certains messages.</string>
|
||||
<string name="dm_relays_not_found_explanation">Les relais de messagerie DM acceptent n\'importe quel message de n\'importe qui, mais vous permet seulement de les télécharger. Par exemple, inbox.nostr.wine fonctionne de cette façon. </string>
|
||||
<string name="dm_relays_not_found_create_now">Configurer maintenant</string>
|
||||
<string name="zap_the_devs_title">Zap les Devs !</string>
|
||||
<string name="zap_the_devs_description">Votre don nous aide à faire la différence. Chaque sat compte !</string>
|
||||
<string name="donate_now">Faire un don maintenant</string>
|
||||
|
@ -19,32 +19,80 @@
|
||||
<string name="relay_icon">Ikona retransmitera</string>
|
||||
<string name="unknown_author">Autor nieznany</string>
|
||||
<string name="copy_text">Skopiuj tekst</string>
|
||||
<string name="copy_user_pubkey">Kopiuj identyfikator autora</string>
|
||||
<string name="copy_note_id">Kopiuj identyfikator notatki</string>
|
||||
<string name="block_report">Zablokuj / Zgłoś</string>
|
||||
<string name="block_hide_user"><![CDATA[Zablokuj i ukryj użytkownika]]></string>
|
||||
<string name="report_spam_scam">Zgłoś spam/oszustwo</string>
|
||||
<string name="report_impersonation">Zgłoś podszywanie się</string>
|
||||
<string name="report_explicit_content">Zgłoś niedozwoloną zawartość</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_reply">Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc odpowiedzieć</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_boost_posts">Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby zwiększyć liczbę postów</string>
|
||||
<string name="login_with_a_private_key_to_like_posts">Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby polubić posty</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc wysyłać zapy</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_follow">Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc obserwować</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_unfollow">Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc przestać obserwować</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_hide_word">Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc ukryć słowo lub zdanie</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_show_word">Używasz klucza publicznego, a klucze publiczne są tylko do odczytu. Zaloguj się za pomocą klucza prywatnego, aby móc pokazać słowo lub zdanie</string>
|
||||
<string name="zaps">Zapy</string>
|
||||
<string name="view_count">Liczba wyświetleń</string>
|
||||
<string name="boost">Zwiększ</string>
|
||||
<string name="boosted">zwiększony</string>
|
||||
<string name="edited">edytowano</string>
|
||||
<string name="edited_number">edytuj #%1$s</string>
|
||||
<string name="original">oryginalny</string>
|
||||
<string name="new_amount_in_sats">Nowa kwota w Satsach</string>
|
||||
<string name="add">Dodaj</string>
|
||||
<string name="and">" i "</string>
|
||||
<string name="in_channel">"na kanale "</string>
|
||||
<string name="profile_banner">Baner profilu</string>
|
||||
<string name="payment_successful">Płatność zakończona pomyślnie</string>
|
||||
<string name="error_parsing_error_message">Błąd podczas analizowania komunikatu o błędzie</string>
|
||||
<string name="following">" Obserwowani"</string>
|
||||
<string name="followers">" Obserwujący"</string>
|
||||
<string name="profile">Profil</string>
|
||||
<string name="security_filters">Filtry bezpieczeństwa</string>
|
||||
<string name="log_out">Wyloguj się</string>
|
||||
<string name="show_more">Pokaż Więcej</string>
|
||||
<string name="pay">Zapłać</string>
|
||||
<string name="note_to_receiver">Notatka dla odbiorcy</string>
|
||||
<string name="thank_you_so_much">Dziękuję bardzo!</string>
|
||||
<string name="amount_in_sats">Kwota w Satsach</string>
|
||||
<string name="send_sats">Wyślij Satsy</string>
|
||||
<string name="error_parsing_preview_for">"Błąd analizowania podglądu dla %1$s : %2$s"</string>
|
||||
<string name="preview_card_image_for">"Podgląd obrazu karty dla %1$s"</string>
|
||||
<string name="new_channel">Nowy kanał</string>
|
||||
<string name="channel_name">Nazwa kanału</string>
|
||||
<string name="my_awesome_group">Moja wspaniała Grupa</string>
|
||||
<string name="picture_url">Adres URL zdjęcia</string>
|
||||
<string name="description">Opis</string>
|
||||
<string name="about_us">"O nas. "</string>
|
||||
<string name="what_s_on_your_mind">Co masz na myśli?</string>
|
||||
<string name="post">Wpis</string>
|
||||
<string name="save">Zapisz</string>
|
||||
<string name="create">Utwórz</string>
|
||||
<string name="cancel">Anuluj</string>
|
||||
<string name="relay_address">Adres retransmitera</string>
|
||||
<string name="posts">Wpisy</string>
|
||||
<string name="errors">Błędy</string>
|
||||
<string name="add_a_relay">Dodaj Retransmiter</string>
|
||||
<string name="username">Nazwa użytkownika</string>
|
||||
<string name="about_me">O mnie</string>
|
||||
<string name="avatar_url">URL awatara</string>
|
||||
<string name="banner_url">URL banera</string>
|
||||
<string name="website_url">Adres URL strony</string>
|
||||
<string name="upload_image">Dodaj zdjęcie</string>
|
||||
<string name="blocked_users">Zablokowani użytkownicy</string>
|
||||
<string name="notes">Notatki</string>
|
||||
<string name="replies">Odpowiedzi</string>
|
||||
<string name="reports">"Zgłoszenia"</string>
|
||||
<string name="more_options">Więcej opcji</string>
|
||||
<string name="relays">" Retransmitery"</string>
|
||||
<string name="website">Strona www</string>
|
||||
<string name="send_a_direct_message">Wyślij bezpośrednią wiadomość</string>
|
||||
<string name="edits_the_user_s_metadata">Edytowanie metadanych użytkowników</string>
|
||||
<string name="follow">Obserwuj</string>
|
||||
<string name="follow_back">Również obserwuj</string>
|
||||
<string name="unblock">Odblokuj</string>
|
||||
<string name="copy_user_id">Kopiuj ID użytkownika</string>
|
||||
<string name="unblock_user">Odblokuj użytkownika</string>
|
||||
@ -52,13 +100,16 @@
|
||||
<string name="clear">Wyczyść</string>
|
||||
<string name="app_logo">Logo aplikacji</string>
|
||||
<string name="nsec_npub_hex_private_key">nsec. lub npub.</string>
|
||||
<string name="ncryptsec_password">hasło, aby otworzyć klucz</string>
|
||||
<string name="show_password">Pokaż hasło</string>
|
||||
<string name="hide_password">Ukryj hasło</string>
|
||||
<string name="invalid_key">Nieprawidłowy klucz</string>
|
||||
<string name="invalid_key_with_message">Nieprawidłowy klucz: %1$s</string>
|
||||
<string name="i_accept_the">"Akceptuję "</string>
|
||||
<string name="terms_of_use">warunki użytkowania</string>
|
||||
<string name="acceptance_of_terms_is_required">Wymagane jest zaakceptowanie warunków użytkowania</string>
|
||||
<string name="password_is_required">Hasło jest wymagane</string>
|
||||
<string name="key_is_required">Klucz jest wymagany</string>
|
||||
<string name="login">Zaloguj się</string>
|
||||
<string name="sign_up">Zarejestruj się</string>
|
||||
<string name="create_account">Utwórz konto</string>
|
||||
@ -75,18 +126,65 @@
|
||||
<string name="unfollow">Przestań obserwować</string>
|
||||
<string name="public_chat">Czat Publiczny</string>
|
||||
<string name="remove">Usuń</string>
|
||||
<string name="translations_to">do</string>
|
||||
<string name="nip_05">Adres Nostr</string>
|
||||
<string name="never">nigdy</string>
|
||||
<string name="now">teraz</string>
|
||||
<string name="h">godz.</string>
|
||||
<string name="nudity">Nagość</string>
|
||||
<string name="profanity_hateful_speech">Wulgaryzmy / Mowa nienawiści</string>
|
||||
<string name="report_hateful_speech">Zgłoś nienawistną mowę</string>
|
||||
<string name="report_nudity_porn">Zgłoś Nagość / Pornografię</string>
|
||||
<string name="others">inne</string>
|
||||
<string name="mark_all_as_read">Oznacz wszystkie jako przeczytane</string>
|
||||
<string name="biometric_error">Błąd</string>
|
||||
<string name="select_text_dialog_top">Zaznacz tekst</string>
|
||||
<string name="account_switch_add_account_dialog_title">Dodaj nowe konto</string>
|
||||
<string name="drawer_accounts">Konta</string>
|
||||
<string name="account_switch_select_account">Wybierz Konto</string>
|
||||
<string name="account_switch_add_account_btn">Dodaj konto</string>
|
||||
<string name="account_switch_pubkey_only">Tylko do odczytu, brak klucza prywatnego</string>
|
||||
<string name="back">Wstecz</string>
|
||||
<string name="quick_action_select">Wybierz</string>
|
||||
<string name="quick_action_share">Udostępnij</string>
|
||||
<string name="quick_action_copy_user_id">ID autora</string>
|
||||
<string name="quick_action_copy_note_id">ID notatki</string>
|
||||
<string name="quick_action_copy_text">Skopiuj tekst</string>
|
||||
<string name="quick_action_delete">Usuń</string>
|
||||
<string name="quick_action_unfollow">Przestań obserwować</string>
|
||||
<string name="quick_action_follow">Śledź</string>
|
||||
<string name="quick_action_request_deletion_alert_title">Poproś o usunięcie</string>
|
||||
<string name="quick_action_request_deletion_alert_body">Amethyst poprosi o usunięcie Twojej notatki z aktualnie podłączonych retransmitorów. Nie ma gwarancji, że Twoja notatka zostanie trwale usunięta z tych retransmitorów lub z innych retransmitorów, gdzie może być przechowywana.</string>
|
||||
<string name="quick_action_block_dialog_btn">Zablokuj</string>
|
||||
<string name="quick_action_delete_dialog_btn">Usuń</string>
|
||||
<string name="quick_action_block">Zablokuj</string>
|
||||
<string name="quick_action_report">Zgłoś</string>
|
||||
<string name="quick_action_delete_button">Usuń</string>
|
||||
<string name="quick_action_dont_show_again_button">Nie pokazuj ponownie</string>
|
||||
<string name="report_dialog_spam">Spam lub oszustwa</string>
|
||||
<string name="report_dialog_profanity">Wulgaryzmy lub nienawistne zachowanie</string>
|
||||
<string name="report_dialog_impersonation">Złośliwe podszywanie się</string>
|
||||
<string name="report_dialog_nudity">Nagość lub zawartość graficzna</string>
|
||||
<string name="report_dialog_illegal">Nielegalne zachowanie</string>
|
||||
<string name="report_dialog_blocking_a_user">Zablokowanie użytkownika ukryje jego zawartość w aplikacji. Twoje notatki są nadal widoczne publicznie, w tym dla osób, które blokujesz. Zablokowani użytkownicy są wymienieni na ekranie filtrów bezpieczeństwa.</string>
|
||||
<string name="report_dialog_block_hide_user_btn"><![CDATA[Zablokuj i ukryj użytkownika]]></string>
|
||||
<string name="report_dialog_report_btn">Zgłoś nadużycie</string>
|
||||
<string name="report_dialog_additional_reason_label">Dodatkowe informacje</string>
|
||||
<string name="report_dialog_select_reason_label">Powód</string>
|
||||
<string name="report_dialog_select_reason_placeholder">Wybierz powód…</string>
|
||||
<string name="report_dialog_post_report_btn">Wyślij zgłoszenie</string>
|
||||
<string name="report_dialog_title">Zablokuj i zgłoś</string>
|
||||
<string name="block_only">Zablokuj</string>
|
||||
<string name="bookmarks">Zakładki</string>
|
||||
<string name="drafts">Projekty</string>
|
||||
<string name="private_bookmarks">Prywatne Zakładki</string>
|
||||
<string name="public_bookmarks">Publiczne zakładki</string>
|
||||
<string name="add_to_private_bookmarks">Dodaj do prywatnych zakładek</string>
|
||||
<string name="add_to_public_bookmarks">Dodaj do publicznych zakładek</string>
|
||||
<string name="remove_from_private_bookmarks">Usuń z prywatnych zakładek</string>
|
||||
<string name="remove_from_public_bookmarks">Usuń z publicznych zakładek</string>
|
||||
<string name="wallet_connect_service_show_secret">Pokaż tajny klucz</string>
|
||||
<string name="poll_consensus_threshold_percent">(0–100)%</string>
|
||||
<string name="custom_zaps_add_a_message">Dodaj wiadomość publiczną</string>
|
||||
<string name="custom_zaps_add_a_message_private">Dodaj prywatną wiadomość</string>
|
||||
<string name="custom_zaps_add_a_message_example">Dziękujemy za całą twoją pracę!</string>
|
||||
@ -97,13 +195,103 @@
|
||||
<string name="zap_type_private_explainer">Nadawca i odbiorca mogą zobaczyć się nawzajem i przeczytać wiadomość</string>
|
||||
<string name="upload_server_relays_nip95">Twoje retransmitery (NIP-95)</string>
|
||||
<string name="upload_server_relays_nip95_explainer">Pliki są przechowywane przez Twoje retransmitery. Nowy NIP: sprawdź, czy jest obsługiwany</string>
|
||||
<string name="do_you_really_want_to_disable_tor_text">Twoje dane zostaną natychmiast przekazane w ramach zwykłej sieci</string>
|
||||
<string name="yes">Tak</string>
|
||||
<string name="no">Nie</string>
|
||||
<string name="follow_list_selection">Lista obserwowanych</string>
|
||||
<string name="invalid_port_number">Nieprawidłowy numer portu</string>
|
||||
<string name="app_notification_dms_channel_name">Prywatne Wiadomości</string>
|
||||
<string name="app_notification_dms_channel_description">Powiadamia Cię, gdy nadejdzie prywatna wiadomość</string>
|
||||
<string name="app_notification_zaps_channel_name">Otrzymano Zapy</string>
|
||||
<string name="app_notification_zaps_channel_description">Powiadamia Cię, gdy ktoś prześle ci zapy</string>
|
||||
<string name="channel_list_join_conversation">Dołącz do rozmowy</string>
|
||||
<string name="channel_list_user_or_group_id">ID Użytkownika lub Grupy</string>
|
||||
<string name="channel_list_user_or_group_id_demo">npub, nevent lub hex</string>
|
||||
<string name="channel_list_create_channel">Utwórz</string>
|
||||
<string name="channel_list_join_channel">Dołącz</string>
|
||||
<string name="today">Dzisiaj</string>
|
||||
<string name="content_warning">Ostrzeżenie o zawartości</string>
|
||||
<string name="content_warning_explanation">Ten post zawiera wrażliwe treści, które niektóre osoby mogą uznać za obraźliwe lub niepokojące</string>
|
||||
<string name="content_warning_hide_all_sensitive_content">Zawsze ukrywaj wrażliwe treści</string>
|
||||
<string name="content_warning_show_all_sensitive_content">Zawsze pokazuj wrażliwą zawartość</string>
|
||||
<string name="content_warning_see_warnings">Zawsze pokazuj ostrzeżenia dotyczące zawartości</string>
|
||||
<string name="filter_spam_from_strangers">Filtrowanie spamu od nieznajomych</string>
|
||||
<string name="warn_when_posts_have_reports_from_your_follows">Ostrzegaj, gdy posty zostały zgłoszone przez osoby które obserwujesz</string>
|
||||
<string name="read_from_relay">Odczytaj z Retransmitera</string>
|
||||
<string name="write_to_relay">Zapisz do Retransmitera</string>
|
||||
<string name="an_error_occurred_trying_to_get_relay_information">Wystąpił błąd podczas próby uzyskania informacji o retransmiterze z %1$s</string>
|
||||
<string name="owner">Właściciel</string>
|
||||
<string name="version">Wersja</string>
|
||||
<string name="software">Oprogramowanie</string>
|
||||
<string name="contact">Kontakt</string>
|
||||
<string name="payments_url">Adres URL płatności</string>
|
||||
<string name="limitations">Ograniczenia</string>
|
||||
<string name="countries">Kraje</string>
|
||||
<string name="languages">Języki</string>
|
||||
<string name="posting_policy">Polityka publikowania</string>
|
||||
<string name="message_length">Długość wiadomości</string>
|
||||
<string name="subscriptions">Subskrybcje</string>
|
||||
<string name="filters">Filtry</string>
|
||||
<string name="relay_setup">Retransmitery</string>
|
||||
<string name="discover_community">Społeczność</string>
|
||||
<string name="discover_chat">Czaty</string>
|
||||
<string name="community_approved_posts">Zatwierdzone posty</string>
|
||||
<string name="groups_no_descriptor">Ta grupa nie ma opisu ani reguł. Porozmawiaj z właścicielem, aby je dodać</string>
|
||||
<string name="community_no_descriptor">Ta społeczność nie ma opisu. Porozmawiaj z właścicielem, aby go dodać</string>
|
||||
<string name="add_sensitive_content_label">Treść wrażliwa</string>
|
||||
<string name="add_sensitive_content_description">Dodaje ostrzeżenie o wrażliwej treści przed wyświetleniem tej zawartości</string>
|
||||
<string name="settings">Ustawienia</string>
|
||||
<string name="connectivity_type_always">Zawsze</string>
|
||||
<string name="connectivity_type_wifi_only">Tylko WiFi</string>
|
||||
<string name="connectivity_type_never">Nigdy</string>
|
||||
<string name="language">Język</string>
|
||||
<string name="theme">Motyw</string>
|
||||
<string name="automatically_load_images_gifs">Podgląd obrazu</string>
|
||||
<string name="automatically_play_videos">Odtwarzanie wideo</string>
|
||||
<string name="automatically_show_url_preview">Podgląd URL</string>
|
||||
<string name="load_image">Załaduj obraz</string>
|
||||
<string name="spamming_users">Spamerzy</string>
|
||||
<string name="muted_button">Wyciszone. Kliknij, aby wyłączyć wyciszenie</string>
|
||||
<string name="mute_button">Dźwięk włączony. Kliknij, aby wyciszyć</string>
|
||||
<string name="nip05_verified">Adres Nostr został zweryfikowany</string>
|
||||
<string name="nip05_failed">Nieudana weryfikacja adresu Nostr</string>
|
||||
<string name="nip05_checking">Sprawdzanie adresu Nostr</string>
|
||||
<string name="select_deselect_all">Zaznacz/Odznacz wszystko</string>
|
||||
<string name="select_a_relay_to_continue">Wybierz retransmiter, aby kontynuować</string>
|
||||
<string name="zap_forward_title">Przekaż zapy do:</string>
|
||||
<string name="messages_new_message_to">Do</string>
|
||||
<string name="messages_new_message_subject">Temat</string>
|
||||
<string name="messages_new_message_subject_caption">Temat dyskusji</string>
|
||||
<string name="messages_new_message_to_caption">"\@Użytkownik1, @Użytkownik2, @Użytkownik3"</string>
|
||||
<string name="messages_group_descriptor">Członkowie tej grupy</string>
|
||||
<string name="copy_to_clipboard">Kopiuj do schowka</string>
|
||||
<string name="copy_npub_to_clipboard">Kopiuj npub do schowka</string>
|
||||
<string name="copy_url_to_clipboard">Kopiuj adres URL do schowka</string>
|
||||
<string name="copy_the_note_id_to_the_clipboard">Kopiuj ID notatki do schowka</string>
|
||||
<string name="status_update">Zaktualizuj status</string>
|
||||
<string name="lightning_wallets_not_found">Błąd podczas analizowania komunikatu o błędzie</string>
|
||||
<string name="zap_split_weight_placeholder">25</string>
|
||||
<string name="automatically_show_profile_picture">Zdjęcie profilowe</string>
|
||||
<string name="automatically_show_profile_picture_description">Pokaż zdjęcia profilowe</string>
|
||||
<string name="select_an_option">Wybierz opcję</string>
|
||||
<string name="unable_to_download_relay_document">Nie można pobrać dokumentu retransmitera</string>
|
||||
<string name="classifieds_title_placeholder">iPhone 13</string>
|
||||
<string name="classifieds_price">Cena (w Satach)</string>
|
||||
<string name="classifieds_location_placeholder">Miasto, Województwo, Kraj</string>
|
||||
<string name="classifieds_category_accessories">Akcesoria</string>
|
||||
<string name="classifieds_category_electronics">Elektronika</string>
|
||||
<string name="classifieds_category_furniture">Meble</string>
|
||||
<string name="classifieds_category_books">Książki</string>
|
||||
<string name="classifieds_category_pets">Zwierzęta domowe</string>
|
||||
<string name="classifieds_category_sports">Sporty</string>
|
||||
<string name="classifieds_category_fitness">Fitness</string>
|
||||
<string name="classifieds_category_art">Sztuka</string>
|
||||
<string name="classifieds_category_office">Biuro</string>
|
||||
<string name="relay_info">Retransmiter %1$s</string>
|
||||
<string name="expand_relay_list">Rozwiń listę retransmiterów</string>
|
||||
<string name="relay_list_selector">Wybór listy retransmiterów</string>
|
||||
<string name="invalid_nip19_uri">Nieprawidłowy adres</string>
|
||||
<string name="dm_relays_not_found_create_now">Skonfiguruj teraz</string>
|
||||
<string name="thank_you">Dziękuję!</string>
|
||||
<string name="max_limit">Maksymalny Limit</string>
|
||||
</resources>
|
||||
|
@ -490,6 +490,7 @@
|
||||
|
||||
<string name="relay_setup">Relays</string>
|
||||
|
||||
<string name="discover_content">Note Discovery</string>
|
||||
<string name="discover_marketplace">Marketplace</string>
|
||||
<string name="discover_live">Live</string>
|
||||
<string name="discover_community">Community</string>
|
||||
@ -845,4 +846,7 @@
|
||||
<string name="draft_note">Draft Note</string>
|
||||
|
||||
<string name="load_from_text">From Msg</string>
|
||||
|
||||
<string name="dvm_waiting_status">Waiting for DVM to reply</string>
|
||||
<string name="dvm_no_status">DVM seems not to reply</string>
|
||||
</resources>
|
||||
|
@ -22,11 +22,94 @@ package com.vitorpamplona.quartz.events
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
@Stable
|
||||
class AppMetadata {
|
||||
var name: String? = null
|
||||
var username: String? = null
|
||||
|
||||
@JsonProperty("display_name")
|
||||
var displayName: String? = null
|
||||
var picture: String? = null
|
||||
|
||||
var banner: String? = null
|
||||
var image: String? = null
|
||||
var website: String? = null
|
||||
var about: String? = null
|
||||
var subscription: Boolean? = false
|
||||
var cashuAccepted: Boolean? = false
|
||||
var encryptionSupported: Boolean? = false
|
||||
var personalized: Boolean? = false
|
||||
var amount: String? = null
|
||||
|
||||
var nip05: String? = null
|
||||
var domain: String? = null
|
||||
var lud06: String? = null
|
||||
var lud16: String? = null
|
||||
|
||||
var twitter: String? = null
|
||||
|
||||
@Transient
|
||||
var tags: ImmutableListOfLists<String>? = null
|
||||
|
||||
fun anyName(): String? {
|
||||
return displayName ?: name ?: username
|
||||
}
|
||||
|
||||
fun anyNameStartsWith(prefix: String): Boolean {
|
||||
return listOfNotNull(name, username, displayName, nip05, lud06, lud16).any {
|
||||
it.contains(prefix, true)
|
||||
}
|
||||
}
|
||||
|
||||
fun lnAddress(): String? {
|
||||
return lud16 ?: lud06
|
||||
}
|
||||
|
||||
fun bestName(): String? {
|
||||
return displayName ?: name ?: username
|
||||
}
|
||||
|
||||
fun nip05(): String? {
|
||||
return nip05
|
||||
}
|
||||
|
||||
fun profilePicture(): String? {
|
||||
return picture
|
||||
}
|
||||
|
||||
fun cleanBlankNames() {
|
||||
if (picture?.isNotEmpty() == true) picture = picture?.trim()
|
||||
if (nip05?.isNotEmpty() == true) nip05 = nip05?.trim()
|
||||
|
||||
if (displayName?.isNotEmpty() == true) displayName = displayName?.trim()
|
||||
if (name?.isNotEmpty() == true) name = name?.trim()
|
||||
if (username?.isNotEmpty() == true) username = username?.trim()
|
||||
if (lud06?.isNotEmpty() == true) lud06 = lud06?.trim()
|
||||
if (lud16?.isNotEmpty() == true) lud16 = lud16?.trim()
|
||||
|
||||
if (website?.isNotEmpty() == true) website = website?.trim()
|
||||
if (domain?.isNotEmpty() == true) domain = domain?.trim()
|
||||
|
||||
if (picture?.isBlank() == true) picture = null
|
||||
if (nip05?.isBlank() == true) nip05 = null
|
||||
if (displayName?.isBlank() == true) displayName = null
|
||||
if (name?.isBlank() == true) name = null
|
||||
if (username?.isBlank() == true) username = null
|
||||
if (lud06?.isBlank() == true) lud06 = null
|
||||
if (lud16?.isBlank() == true) lud16 = null
|
||||
|
||||
if (website?.isBlank() == true) website = null
|
||||
if (domain?.isBlank() == true) domain = null
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
class AppDefinitionEvent(
|
||||
id: HexKey,
|
||||
@ -36,7 +119,7 @@ class AppDefinitionEvent(
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
@Transient private var cachedMetadata: UserMetadata? = null
|
||||
@Transient private var cachedMetadata: AppMetadata? = null
|
||||
|
||||
fun appMetaData() =
|
||||
if (cachedMetadata != null) {
|
||||
@ -46,7 +129,7 @@ class AppDefinitionEvent(
|
||||
val newMetadata =
|
||||
mapper.readValue(
|
||||
ByteArrayInputStream(content.toByteArray(Charsets.UTF_8)),
|
||||
UserMetadata::class.java,
|
||||
AppMetadata::class.java,
|
||||
)
|
||||
|
||||
cachedMetadata = newMetadata
|
||||
|
@ -114,6 +114,11 @@ class EventFactory {
|
||||
MetadataEvent.KIND -> MetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
MuteListEvent.KIND -> MuteListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NNSEvent.KIND -> NNSEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90StatusEvent.KIND -> NIP90StatusEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90ContentDiscoveryRequestEvent.KIND -> NIP90ContentDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90ContentDiscoveryResponseEvent.KIND -> NIP90ContentDiscoveryResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90UserDiscoveryRequestEvent.KIND -> NIP90UserDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
NIP90UserDiscoveryResponseEvent.KIND -> NIP90UserDiscoveryResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
OtsEvent.KIND -> OtsEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PeopleListEvent.KIND -> PeopleListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
PinListEvent.KIND -> PinListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||
|
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.events
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Stable
|
||||
@Immutable
|
||||
class NIP90ContentDiscoveryRequestEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
companion object {
|
||||
const val KIND = 5300
|
||||
|
||||
fun create(
|
||||
addressedDVM: String,
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (NIP90ContentDiscoveryRequestEvent) -> Unit,
|
||||
) {
|
||||
val content = ""
|
||||
val tags = mutableListOf<Array<String>>()
|
||||
tags.add(arrayOf("p", addressedDVM))
|
||||
tags.add(arrayOf("alt", "NIP90 Content Discovery request"))
|
||||
tags.add(arrayOf("client", "Amethyst"))
|
||||
signer.sign(createdAt, KIND, tags.toTypedArray(), content, onReady)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.events
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
class NIP90ContentDiscoveryResponseEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
companion object {
|
||||
const val KIND = 6300
|
||||
const val ALT = "NIP90 Content Discovery reply"
|
||||
|
||||
fun create(
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (AppRecommendationEvent) -> Unit,
|
||||
) {
|
||||
val tags =
|
||||
arrayOf(
|
||||
arrayOf("alt", ALT),
|
||||
)
|
||||
signer.sign(createdAt, KIND, tags, "", onReady)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.events
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
class NIP90StatusEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
companion object {
|
||||
const val KIND = 7000
|
||||
const val ALT = "NIP90 Status update"
|
||||
|
||||
fun create(
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (AppRecommendationEvent) -> Unit,
|
||||
) {
|
||||
val tags =
|
||||
arrayOf(
|
||||
arrayOf("alt", ALT),
|
||||
)
|
||||
signer.sign(createdAt, KIND, tags, "", onReady)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.events
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
class NIP90UserDiscoveryRequestEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
companion object {
|
||||
const val KIND = 5301
|
||||
const val ALT = "NIP90 Content Discovery request"
|
||||
|
||||
fun create(
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (AppRecommendationEvent) -> Unit,
|
||||
) {
|
||||
val tags =
|
||||
arrayOf(
|
||||
arrayOf("alt", ALT),
|
||||
)
|
||||
signer.sign(createdAt, KIND, tags, "", onReady)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.events
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
class NIP90UserDiscoveryResponseEvent(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
sig: HexKey,
|
||||
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||
companion object {
|
||||
const val KIND = 6301
|
||||
const val ALT = "NIP90 Content Discovery reply"
|
||||
|
||||
fun create(
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (AppRecommendationEvent) -> Unit,
|
||||
) {
|
||||
val tags =
|
||||
arrayOf(
|
||||
arrayOf("alt", ALT),
|
||||
)
|
||||
signer.sign(createdAt, KIND, tags, "", onReady)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user