add draft in the home feed

This commit is contained in:
greenart7c3 2024-03-15 09:08:35 -03:00
parent fa5d992010
commit e292affbe6
17 changed files with 334 additions and 106 deletions

View File

@ -56,6 +56,7 @@ import com.vitorpamplona.quartz.events.ClassifiedsEvent
import com.vitorpamplona.quartz.events.Contact
import com.vitorpamplona.quartz.events.ContactListEvent
import com.vitorpamplona.quartz.events.DeletionEvent
import com.vitorpamplona.quartz.events.DraftEvent
import com.vitorpamplona.quartz.events.EmojiPackEvent
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
import com.vitorpamplona.quartz.events.EmojiUrl
@ -1422,6 +1423,7 @@ class Account(
relayList: List<Relay>? = null,
geohash: String? = null,
nip94attachments: List<FileHeaderEvent>? = null,
draftTag: String?,
) {
if (!isWriteable()) return
@ -1445,20 +1447,28 @@ class Account(
nip94attachments = nip94attachments,
forkedFrom = forkedFrom,
signer = signer,
isDraft = draftTag != null,
) {
Client.send(it, relayList = relayList)
LocalCache.justConsume(it, null)
// broadcast replied notes
replyingTo?.let {
LocalCache.getNoteIfExists(replyingTo)?.event?.let {
Client.send(it, relayList = relayList)
if (draftTag != null) {
DraftEvent.create(draftTag, it, signer) { draftEvent ->
Client.send(draftEvent, relayList = relayList)
LocalCache.justConsume(draftEvent, null)
}
}
replyTo?.forEach { it.event?.let { Client.send(it, relayList = relayList) } }
addresses?.forEach {
LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let {
Client.send(it, relayList = relayList)
} else {
Client.send(it, relayList = relayList)
LocalCache.justConsume(it, null)
// broadcast replied notes
replyingTo?.let {
LocalCache.getNoteIfExists(replyingTo)?.event?.let {
Client.send(it, relayList = relayList)
}
}
replyTo?.forEach { it.event?.let { Client.send(it, relayList = relayList) } }
addresses?.forEach {
LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let {
Client.send(it, relayList = relayList)
}
}
}
}
@ -2210,6 +2220,7 @@ class Account(
fun cachedDecryptContent(note: Note): String? {
val event = note.event
return if (event is PrivateDmEvent && isWriteable()) {
event.cachedContentFor(signer)
} else if (event is LnZapRequestEvent && event.isPrivateZap() && isWriteable()) {

View File

@ -61,6 +61,7 @@ import com.vitorpamplona.quartz.events.CommunityListEvent
import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.events.ContactListEvent
import com.vitorpamplona.quartz.events.DeletionEvent
import com.vitorpamplona.quartz.events.DraftEvent
import com.vitorpamplona.quartz.events.EmojiPackEvent
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
import com.vitorpamplona.quartz.events.Event
@ -2042,6 +2043,13 @@ object LocalCache {
}
}
private fun consume(
event: DraftEvent,
relay: Relay?,
) {
consumeBaseReplaceable(event, relay)
}
fun justConsume(
event: Event,
relay: Relay?,
@ -2079,6 +2087,7 @@ object LocalCache {
}
is ContactListEvent -> consume(event)
is DeletionEvent -> consume(event)
is DraftEvent -> consume(event, relay)
is EmojiPackEvent -> consume(event, relay)
is EmojiPackSelectionEvent -> consume(event, relay)
is SealedGossipEvent -> consume(event, relay)

View File

@ -106,6 +106,7 @@ open class Note(val idHex: String) {
var event: EventInterface? = null
var author: User? = null
var replyTo: List<Note>? = null
var draft: String? = null
// These fields are updated every time an event related to this note is received.
var replies = listOf<Note>()
@ -183,6 +184,17 @@ open class Note(val idHex: String) {
open fun createdAt() = event?.createdAt()
fun updateDraft(id: String) {
draft = id
}
fun isDraft(): Boolean {
draft?.let {
return it.isNotBlank()
}
return false
}
fun loadEvent(
event: Event,
author: User,

View File

@ -40,6 +40,7 @@ import com.vitorpamplona.quartz.events.CalendarRSVPEvent
import com.vitorpamplona.quartz.events.CalendarTimeSlotEvent
import com.vitorpamplona.quartz.events.ChannelMessageEvent
import com.vitorpamplona.quartz.events.ContactListEvent
import com.vitorpamplona.quartz.events.DraftEvent
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.EventInterface
@ -229,6 +230,16 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
)
}
fun createDraftsFilter() =
TypedFilter(
types = COMMON_FEED_TYPES,
filter =
JsonFilter(
kinds = listOf(DraftEvent.KIND),
authors = listOf(account.userProfile().pubkeyHex),
),
)
fun createGiftWrapsToMeFilter() =
TypedFilter(
types = COMMON_FEED_TYPES,
@ -262,22 +273,38 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
checkNotInMainThread()
if (LocalCache.justVerify(event)) {
if (event is GiftWrapEvent) {
// Avoid decrypting over and over again if the event already exist.
val note = LocalCache.getNoteIfExists(event.id)
if (note != null && relay.brief in note.relays) return
when (event) {
is DraftEvent -> {
// Avoid decrypting over and over again if the event already exist.
val note = LocalCache.getNoteIfExists(event.id)
if (note != null && relay.brief in note.relays) return
event.cachedGift(account.signer) { this.consume(it, relay) }
}
event.plainContent(account.signer) {
LocalCache.justConsume(it, relay)
val draftNote = LocalCache.getNoteIfExists(it.id)
draftNote?.updateDraft(event.id)
}
}
if (event is SealedGossipEvent) {
// Avoid decrypting over and over again if the event already exist.
val note = LocalCache.getNoteIfExists(event.id)
if (note != null && relay.brief in note.relays) return
is GiftWrapEvent -> {
// Avoid decrypting over and over again if the event already exist.
val note = LocalCache.getNoteIfExists(event.id)
if (note != null && relay.brief in note.relays) return
event.cachedGossip(account.signer) { LocalCache.justConsume(it, relay) }
} else {
LocalCache.justConsume(event, relay)
event.cachedGift(account.signer) { this.consume(it, relay) }
}
is SealedGossipEvent -> {
// Avoid decrypting over and over again if the event already exist.
val note = LocalCache.getNoteIfExists(event.id)
if (note != null && relay.brief in note.relays) return
event.cachedGossip(account.signer) { LocalCache.justConsume(it, relay) }
}
else -> {
LocalCache.justConsume(event, relay)
}
}
}
}
@ -328,6 +355,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
createAccountSettingsFilter(),
createAccountLastPostsListFilter(),
createOtherAccountsBaseFilter(),
createDraftsFilter(),
)
.ifEmpty { null }
} else {

View File

@ -263,7 +263,7 @@ class Relay(
val subscriptionId = msgArray.get(1).asText()
val event = Event.fromJson(msgArray.get(2))
// Log.w("Relay", "Relay onEVENT ${event.kind} $url, $subscriptionId ${msgArray.get(2)}")
Log.w("Relay", "Relay onEVENT ${event.kind} $url, $subscriptionId ${msgArray.get(2)}")
listeners.forEach {
it.onEvent(
this@Relay,

View File

@ -74,6 +74,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch
import java.util.UUID
enum class UserSuggestionAnchor {
MAIN_MESSAGE,
@ -83,6 +84,7 @@ enum class UserSuggestionAnchor {
@Stable
open class NewPostViewModel() : ViewModel() {
var draftTag: String = UUID.randomUUID().toString()
var accountViewModel: AccountViewModel? = null
var account: Account? = null
var requiresNIP24: Boolean = false
@ -306,11 +308,17 @@ open class NewPostViewModel() : ViewModel() {
}
}
fun sendPost(relayList: List<Relay>? = null) {
viewModelScope.launch(Dispatchers.IO) { innerSendPost(relayList) }
fun sendPost(
relayList: List<Relay>? = null,
localDraft: String? = null,
) {
viewModelScope.launch(Dispatchers.IO) { innerSendPost(relayList, localDraft) }
}
suspend fun innerSendPost(relayList: List<Relay>? = null) {
private suspend fun innerSendPost(
relayList: List<Relay>? = null,
localDraft: String?,
) {
if (accountViewModel == null) {
cancel()
return
@ -555,11 +563,13 @@ open class NewPostViewModel() : ViewModel() {
relayList = relayList,
geohash = geoHash,
nip94attachments = usedAttachments,
draftTag = draftTag,
)
}
}
cancel()
if (localDraft == null) {
cancel()
}
}
fun upload(
@ -693,8 +703,8 @@ open class NewPostViewModel() : ViewModel() {
pTags = pTags?.filter { it != userToRemove }
}
open fun saveDraft(message: String) {
account?.let { LocalPreferences.saveDraft(message, originalNote?.idHex, it) }
open fun saveDraft() {
sendPost(localDraft = draftTag)
}
open fun loadDraft(): String? {
@ -708,9 +718,6 @@ open class NewPostViewModel() : ViewModel() {
}
open fun updateMessage(it: TextFieldValue) {
viewModelScope.launch(Dispatchers.IO) {
saveDraft(it.text)
}
message = it
urlPreview = findUrlInMessage()
@ -732,6 +739,10 @@ open class NewPostViewModel() : ViewModel() {
userSuggestions = emptyList()
}
}
viewModelScope.launch(Dispatchers.IO) {
saveDraft()
}
}
open fun updateToUsers(it: TextFieldValue) {
@ -755,10 +766,16 @@ open class NewPostViewModel() : ViewModel() {
userSuggestions = emptyList()
}
}
viewModelScope.launch(Dispatchers.IO) {
saveDraft()
}
}
open fun updateSubject(it: TextFieldValue) {
subject = it
viewModelScope.launch(Dispatchers.IO) {
saveDraft()
}
}
open fun updateZapForwardTo(it: TextFieldValue) {
@ -785,6 +802,9 @@ open class NewPostViewModel() : ViewModel() {
userSuggestions = emptyList()
}
}
viewModelScope.launch(Dispatchers.IO) {
saveDraft()
}
}
open fun autocompleteWithUser(item: User) {
@ -800,9 +820,6 @@ open class NewPostViewModel() : ViewModel() {
message.text.replaceRange(lastWordStart, it.end, wordToInsert),
TextRange(lastWordStart + wordToInsert.length, lastWordStart + wordToInsert.length),
)
viewModelScope.launch(Dispatchers.IO) {
saveDraft(message.text)
}
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
forwardZapTo.addItem(item)
forwardZapToEditting = TextFieldValue("")
@ -833,6 +850,10 @@ open class NewPostViewModel() : ViewModel() {
userSuggestionsMainMessage = null
userSuggestions = emptyList()
}
viewModelScope.launch(Dispatchers.IO) {
saveDraft()
}
}
private fun newStateMapPollOptions(): SnapshotStateMap<Int, String> {
@ -902,8 +923,8 @@ open class NewPostViewModel() : ViewModel() {
nip94attachments = nip94attachments + event
message = message.insertUrlAtCursor(imageUrl)
saveDraft(message.text)
urlPreview = findUrlInMessage()
saveDraft()
}
},
onError = {
@ -945,10 +966,10 @@ open class NewPostViewModel() : ViewModel() {
note?.let {
message = message.insertUrlAtCursor("nostr:" + it.toNEvent())
saveDraft(message.text)
}
urlPreview = findUrlInMessage()
saveDraft()
}
},
onError = {
@ -969,6 +990,7 @@ open class NewPostViewModel() : ViewModel() {
locUtil?.let {
location =
it.locationStateFlow.mapLatest { it.toGeoHash(GeohashPrecision.KM_5_X_5.digits).toString() }
saveDraft()
}
viewModelScope.launch(Dispatchers.IO) { locUtil?.start() }
}

View File

@ -25,6 +25,7 @@ import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.events.ChannelMessageEvent
import com.vitorpamplona.quartz.events.DraftEvent
import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent
@ -69,7 +70,8 @@ class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter<Not
it.event is TextNoteEvent ||
it.event is PollNoteEvent ||
it.event is ChannelMessageEvent ||
it.event is LiveActivitiesChatMessageEvent
it.event is LiveActivitiesChatMessageEvent ||
it.event is DraftEvent
) &&
(
isGlobal ||

View File

@ -33,6 +33,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
@ -51,6 +52,7 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.distinctUntilChanged
@ -113,6 +115,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.Font14SP
import com.vitorpamplona.amethyst.ui.theme.HalfDoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
import com.vitorpamplona.amethyst.ui.theme.Size25dp
@ -128,6 +131,7 @@ import com.vitorpamplona.amethyst.ui.theme.WidthAuthorPictureModifier
import com.vitorpamplona.amethyst.ui.theme.boostedNoteModifier
import com.vitorpamplona.amethyst.ui.theme.channelNotePictureModifier
import com.vitorpamplona.amethyst.ui.theme.grayText
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
import com.vitorpamplona.amethyst.ui.theme.normalNoteModifier
import com.vitorpamplona.amethyst.ui.theme.normalWithTopMarginNoteModifier
@ -1109,10 +1113,26 @@ fun SecondUserInfoRow(
DisplayPoW(pow)
}
if (note.isDraft()) {
Spacer(StdHorzSpacer)
DisplayDraft()
}
DisplayOts(note, accountViewModel)
}
}
@Composable
fun DisplayDraft() {
Text(
"Draft",
color = MaterialTheme.colorScheme.lessImportantLink,
fontSize = Font14SP,
fontWeight = FontWeight.Bold,
maxLines = 1,
)
}
@Composable
fun FirstUserInfoRow(
baseNote: Note,

View File

@ -185,6 +185,7 @@ class AddBountyAmountViewModel : ViewModel() {
root = null,
directMentions = setOf(),
forkedFrom = null,
draftTag = null,
)
nextAmount = TextFieldValue("")

View File

@ -92,6 +92,7 @@ import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
import com.vitorpamplona.amethyst.ui.components.mockAccountViewModel
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
import com.vitorpamplona.amethyst.ui.note.BlankNote
import com.vitorpamplona.amethyst.ui.note.DisplayDraft
import com.vitorpamplona.amethyst.ui.note.HiddenNote
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture
@ -468,6 +469,10 @@ fun NoteMaster(
DisplayPoW(pow)
}
if (note.isDraft()) {
DisplayDraft()
}
DisplayOts(note, accountViewModel)
}
}

View File

@ -22,6 +22,7 @@ package com.vitorpamplona.quartz.events
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.Nip19Bech32
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
@ -33,26 +34,128 @@ class DraftEvent(
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
companion object {
const val KIND = 31234
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
@Transient private var decryptedContent: Map<HexKey, Event> = mapOf()
@Transient private var citedNotesCache: Set<String>? = null
fun replyTos(): List<HexKey> {
val oldStylePositional = tags.filter { it.size > 1 && it.size <= 3 && it[0] == "e" }.map { it[1] }
val newStyleReply = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "reply" }?.get(1)
val newStyleRoot = tags.lastOrNull { it.size > 3 && it[0] == "e" && it[3] == "root" }?.get(1)
val newStyleReplyTos = listOfNotNull(newStyleReply, newStyleRoot)
return if (newStyleReplyTos.isNotEmpty()) {
newStyleReplyTos
} else {
oldStylePositional
}
}
fun create(
dTag: String,
originalNote: EventInterface,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (DraftEvent) -> Unit,
) {
val tags =
arrayOf(
arrayOf("d", dTag),
arrayOf("k", "${originalNote.kind()}"),
)
fun findCitations(): Set<HexKey> {
citedNotesCache?.let {
return it
}
signer.nip44Encrypt(originalNote.content(), signer.pubKey) {
signer.sign(createdAt, KIND, tags, it, onReady)
val citations = mutableSetOf<HexKey>()
// Removes citations from replies:
val matcher = tagSearch.matcher(content)
while (matcher.find()) {
try {
val tag = matcher.group(1)?.let { tags[it.toInt()] }
if (tag != null && tag.size > 1 && tag[0] == "e") {
citations.add(tag[1])
}
if (tag != null && tag.size > 1 && tag[0] == "a") {
citations.add(tag[1])
}
} catch (e: Exception) {
}
}
val matcher2 = Nip19Bech32.nip19regex.matcher(content)
while (matcher2.find()) {
val type = matcher2.group(2) // npub1
val key = matcher2.group(3) // bech32
val additionalChars = matcher2.group(4) // additional chars
if (type != null) {
val parsed = Nip19Bech32.parseComponents(type, key, additionalChars)?.entity
if (parsed != null) {
when (parsed) {
is Nip19Bech32.NEvent -> citations.add(parsed.hex)
is Nip19Bech32.NAddress -> citations.add(parsed.atag)
is Nip19Bech32.Note -> citations.add(parsed.hex)
is Nip19Bech32.NEmbed -> citations.add(parsed.event.id)
}
}
}
}
citedNotesCache = citations
return citations
}
fun tagsWithoutCitations(): List<String> {
val repliesTo = replyTos()
val tagAddresses =
taggedAddresses().filter {
it.kind != CommunityDefinitionEvent.KIND &&
it.kind != WikiNoteEvent.KIND
}.map { it.toTag() }
if (repliesTo.isEmpty() && tagAddresses.isEmpty()) return emptyList()
val citations = findCitations()
return if (citations.isEmpty()) {
repliesTo + tagAddresses
} else {
repliesTo.filter { it !in citations }
}
}
fun cachedContentFor(): Event? {
return decryptedContent[dTag()]
}
fun plainContent(
signer: NostrSigner,
onReady: (Event) -> Unit,
) {
decryptedContent[dTag()]?.let {
onReady(it)
return
}
signer.nip44Decrypt(content, signer.pubKey) { retVal ->
val event = runCatching { fromJson(retVal) }.getOrNull() ?: return@nip44Decrypt
decryptedContent = decryptedContent + Pair(dTag(), event)
onReady(event)
}
}
companion object {
const val KIND = 31234
fun create(
dTag: String,
originalNote: EventInterface,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (DraftEvent) -> Unit,
) {
val tags = mutableListOf<Array<String>>()
tags.add(arrayOf("d", dTag))
tags.add(arrayOf("k", "${originalNote.kind()}"))
tags.addAll(originalNote.tags().filter { it.size > 1 && it[0] == "e" })
tags.addAll(originalNote.tags().filter { it.size > 1 && it[0] == "a" })
signer.nip44Encrypt(originalNote.toJson(), signer.pubKey) { encryptedContent ->
signer.sign(createdAt, KIND, tags.toTypedArray(), encryptedContent, onReady)
}
}
}
}

View File

@ -79,6 +79,7 @@ class EventFactory {
CommunityPostApprovalEvent(id, pubKey, createdAt, tags, content, sig)
ContactListEvent.KIND -> ContactListEvent(id, pubKey, createdAt, tags, content, sig)
DeletionEvent.KIND -> DeletionEvent(id, pubKey, createdAt, tags, content, sig)
DraftEvent.KIND -> DraftEvent(id, pubKey, createdAt, tags, content, sig)
EmojiPackEvent.KIND -> EmojiPackEvent(id, pubKey, createdAt, tags, content, sig)
EmojiPackSelectionEvent.KIND ->
EmojiPackSelectionEvent(id, pubKey, createdAt, tags, content, sig)

View File

@ -20,7 +20,6 @@
*/
package com.vitorpamplona.quartz.events
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
@ -155,49 +154,4 @@ class NIP24Factory {
}
}
}
fun createTextNoteNIP24(
msg: String,
to: List<HexKey>,
signer: NostrSigner,
replyTos: List<String>? = null,
mentions: List<String>? = null,
addresses: List<ATag>?,
extraTags: List<String>?,
zapReceiver: List<ZapSplitSetup>? = null,
markAsSensitive: Boolean = false,
replyingTo: String?,
root: String?,
directMentions: Set<HexKey>,
zapRaiserAmount: Long? = null,
geohash: String? = null,
onReady: (Result) -> Unit,
) {
val senderPublicKey = signer.pubKey
TextNoteEvent.create(
msg = msg,
signer = signer,
replyTos = replyTos,
mentions = mentions,
zapReceiver = zapReceiver,
root = root,
extraTags = extraTags,
addresses = addresses,
directMentions = directMentions,
replyingTo = replyingTo,
markAsSensitive = markAsSensitive,
zapRaiserAmount = zapRaiserAmount,
geohash = geohash,
) { senderMessage ->
createWraps(senderMessage, to.plus(senderPublicKey).toSet(), signer) { wraps ->
onReady(
Result(
msg = senderMessage,
wraps = wraps,
),
)
}
}
}
}

View File

@ -60,6 +60,7 @@ class TextNoteEvent(
forkedFrom: Event? = null,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
isDraft: Boolean,
onReady: (TextNoteEvent) -> Unit,
) {
val tags = mutableListOf<Array<String>>()
@ -121,7 +122,7 @@ class TextNoteEvent(
}
}
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady)
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady, isDraft)
}
}
}

View File

@ -32,6 +32,7 @@ abstract class NostrSigner(val pubKey: HexKey) {
tags: Array<Array<String>>,
content: String,
onReady: (T) -> Unit,
isDraft: Boolean = false,
)
abstract fun nip04Encrypt(

View File

@ -40,7 +40,13 @@ class NostrSignerExternal(
tags: Array<Array<String>>,
content: String,
onReady: (T) -> Unit,
isDraft: Boolean,
) {
if (isDraft) {
unsignedEvent(createdAt, kind, tags, content, onReady)
return
}
val id = Event.generateId(pubKey, createdAt, kind, tags, content).toHexKey()
val event =
@ -86,6 +92,28 @@ class NostrSignerExternal(
}
}
fun <T : Event> unsignedEvent(
createdAt: Long,
kind: Int,
tags: Array<Array<String>>,
content: String,
onReady: (T) -> Unit,
) {
val id = Event.generateId(pubKey, createdAt, kind, tags, content)
onReady(
EventFactory.create(
id.toHexKey(),
pubKey,
createdAt,
kind,
tags,
content,
"",
) as T,
)
}
override fun nip04Encrypt(
decryptedContent: String,
toPublicKey: HexKey,

View File

@ -38,9 +38,15 @@ class NostrSignerInternal(val keyPair: KeyPair) : NostrSigner(keyPair.pubKey.toH
tags: Array<Array<String>>,
content: String,
onReady: (T) -> Unit,
isDraft: Boolean,
) {
if (keyPair.privKey == null) return
if (isDraft) {
unsignedEvent(createdAt, kind, tags, content, onReady)
return
}
if (isUnsignedPrivateEvent(kind, tags)) {
// this is a private zap
signPrivateZap(createdAt, kind, tags, content, onReady)
@ -82,6 +88,30 @@ class NostrSignerInternal(val keyPair: KeyPair) : NostrSigner(keyPair.pubKey.toH
)
}
fun <T : Event> unsignedEvent(
createdAt: Long,
kind: Int,
tags: Array<Array<String>>,
content: String,
onReady: (T) -> Unit,
) {
if (keyPair.privKey == null) return
val id = Event.generateId(pubKey, createdAt, kind, tags, content)
onReady(
EventFactory.create(
id.toHexKey(),
pubKey,
createdAt,
kind,
tags,
content,
"",
) as T,
)
}
override fun nip04Encrypt(
decryptedContent: String,
toPublicKey: HexKey,