mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-12 19:56:55 +01:00
Merge pull request #749 from greenart7c3/main
save a draft while you are typing the post
This commit is contained in:
@@ -56,6 +56,7 @@ import com.vitorpamplona.quartz.events.ClassifiedsEvent
|
|||||||
import com.vitorpamplona.quartz.events.Contact
|
import com.vitorpamplona.quartz.events.Contact
|
||||||
import com.vitorpamplona.quartz.events.ContactListEvent
|
import com.vitorpamplona.quartz.events.ContactListEvent
|
||||||
import com.vitorpamplona.quartz.events.DeletionEvent
|
import com.vitorpamplona.quartz.events.DeletionEvent
|
||||||
|
import com.vitorpamplona.quartz.events.DraftEvent
|
||||||
import com.vitorpamplona.quartz.events.EmojiPackEvent
|
import com.vitorpamplona.quartz.events.EmojiPackEvent
|
||||||
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
|
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
|
||||||
import com.vitorpamplona.quartz.events.EmojiUrl
|
import com.vitorpamplona.quartz.events.EmojiUrl
|
||||||
@@ -845,7 +846,14 @@ class Account(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun delete(note: Note) {
|
suspend fun delete(note: Note) {
|
||||||
return delete(listOf(note))
|
if (note.isDraft()) {
|
||||||
|
note.event?.let {
|
||||||
|
val drafts = LocalCache.getDrafts(it.id())
|
||||||
|
return delete(drafts)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return delete(listOf(note))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun delete(notes: List<Note>) {
|
suspend fun delete(notes: List<Note>) {
|
||||||
@@ -897,10 +905,17 @@ class Account(
|
|||||||
|
|
||||||
fun broadcast(note: Note) {
|
fun broadcast(note: Note) {
|
||||||
note.event?.let {
|
note.event?.let {
|
||||||
if (it is WrappedEvent && it.host != null) {
|
if (note.isDraft()) {
|
||||||
it.host?.let { hostEvent -> Client.send(hostEvent) }
|
val drafts = LocalCache.getDrafts(it.id())
|
||||||
|
drafts.forEach { draftNote ->
|
||||||
|
broadcast(draftNote)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Client.send(it)
|
if (it is WrappedEvent && it.host != null) {
|
||||||
|
it.host?.let { hostEvent -> Client.send(hostEvent) }
|
||||||
|
} else {
|
||||||
|
Client.send(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -929,6 +944,7 @@ class Account(
|
|||||||
|
|
||||||
fun timestamp(note: Note) {
|
fun timestamp(note: Note) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
if (note.isDraft()) return
|
||||||
|
|
||||||
val id = note.event?.id() ?: note.idHex
|
val id = note.event?.id() ?: note.idHex
|
||||||
|
|
||||||
@@ -1318,6 +1334,7 @@ class Account(
|
|||||||
relayList: List<Relay>? = null,
|
relayList: List<Relay>? = null,
|
||||||
geohash: String? = null,
|
geohash: String? = null,
|
||||||
nip94attachments: List<Event>? = null,
|
nip94attachments: List<Event>? = null,
|
||||||
|
draftTag: String?,
|
||||||
) {
|
) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
@@ -1345,14 +1362,24 @@ class Account(
|
|||||||
geohash = geohash,
|
geohash = geohash,
|
||||||
nip94attachments = nip94attachments,
|
nip94attachments = nip94attachments,
|
||||||
signer = signer,
|
signer = signer,
|
||||||
|
isDraft = draftTag != null,
|
||||||
) {
|
) {
|
||||||
Client.send(it, relayList = relayList)
|
if (draftTag != null) {
|
||||||
LocalCache.justConsume(it, null)
|
DraftEvent.create(draftTag, it, signer) { draftEvent ->
|
||||||
|
Client.send(draftEvent, relayList = relayList)
|
||||||
|
LocalCache.justConsume(draftEvent, null)
|
||||||
|
LocalCache.justConsume(it, null)
|
||||||
|
LocalCache.addDraft(draftTag, draftEvent.id(), it.id())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Client.send(it, relayList = relayList)
|
||||||
|
LocalCache.justConsume(it, null)
|
||||||
|
|
||||||
replyTo?.forEach { it.event?.let { Client.send(it, relayList = relayList) } }
|
replyTo?.forEach { it.event?.let { Client.send(it, relayList = relayList) } }
|
||||||
addresses?.forEach {
|
addresses?.forEach {
|
||||||
LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let {
|
LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let {
|
||||||
Client.send(it, relayList = relayList)
|
Client.send(it, relayList = relayList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1373,6 +1400,7 @@ class Account(
|
|||||||
relayList: List<Relay>? = null,
|
relayList: List<Relay>? = null,
|
||||||
geohash: String? = null,
|
geohash: String? = null,
|
||||||
nip94attachments: List<FileHeaderEvent>? = null,
|
nip94attachments: List<FileHeaderEvent>? = null,
|
||||||
|
draftTag: String?,
|
||||||
) {
|
) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
@@ -1396,20 +1424,30 @@ class Account(
|
|||||||
nip94attachments = nip94attachments,
|
nip94attachments = nip94attachments,
|
||||||
forkedFrom = forkedFrom,
|
forkedFrom = forkedFrom,
|
||||||
signer = signer,
|
signer = signer,
|
||||||
|
isDraft = draftTag != null,
|
||||||
) {
|
) {
|
||||||
Client.send(it, relayList = relayList)
|
if (draftTag != null) {
|
||||||
LocalCache.justConsume(it, null)
|
DraftEvent.create(draftTag, it, signer) { draftEvent ->
|
||||||
|
Client.send(draftEvent, relayList = relayList)
|
||||||
// broadcast replied notes
|
LocalCache.justConsume(draftEvent, null)
|
||||||
replyingTo?.let {
|
LocalCache.justConsume(it, null)
|
||||||
LocalCache.getNoteIfExists(replyingTo)?.event?.let {
|
LocalCache.addDraft(draftTag, draftEvent.id(), it.id())
|
||||||
Client.send(it, relayList = relayList)
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
replyTo?.forEach { it.event?.let { Client.send(it, relayList = relayList) } }
|
Client.send(it, relayList = relayList)
|
||||||
addresses?.forEach {
|
LocalCache.justConsume(it, null)
|
||||||
LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let {
|
|
||||||
Client.send(it, relayList = relayList)
|
// 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1430,6 +1468,7 @@ class Account(
|
|||||||
relayList: List<Relay>? = null,
|
relayList: List<Relay>? = null,
|
||||||
geohash: String? = null,
|
geohash: String? = null,
|
||||||
nip94attachments: List<FileHeaderEvent>? = null,
|
nip94attachments: List<FileHeaderEvent>? = null,
|
||||||
|
draftTag: String?,
|
||||||
) {
|
) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
@@ -1453,20 +1492,30 @@ class Account(
|
|||||||
nip94attachments = nip94attachments,
|
nip94attachments = nip94attachments,
|
||||||
forkedFrom = forkedFrom,
|
forkedFrom = forkedFrom,
|
||||||
signer = signer,
|
signer = signer,
|
||||||
|
isDraft = draftTag != null,
|
||||||
) {
|
) {
|
||||||
Client.send(it, relayList = relayList)
|
if (draftTag != null) {
|
||||||
LocalCache.justConsume(it, null)
|
DraftEvent.create(draftTag, it, signer) { draftEvent ->
|
||||||
|
Client.send(draftEvent, relayList = relayList)
|
||||||
// broadcast replied notes
|
LocalCache.justConsume(draftEvent, null)
|
||||||
replyingTo?.let {
|
LocalCache.justConsume(it, null)
|
||||||
LocalCache.getNoteIfExists(replyingTo)?.event?.let {
|
LocalCache.addDraft(draftTag, draftEvent.id(), it.id())
|
||||||
Client.send(it, relayList = relayList)
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
replyTo?.forEach { it.event?.let { Client.send(it, relayList = relayList) } }
|
Client.send(it, relayList = relayList)
|
||||||
addresses?.forEach {
|
LocalCache.justConsume(it, null)
|
||||||
LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let {
|
|
||||||
Client.send(it, relayList = relayList)
|
// 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1510,6 +1559,7 @@ class Account(
|
|||||||
relayList: List<Relay>? = null,
|
relayList: List<Relay>? = null,
|
||||||
geohash: String? = null,
|
geohash: String? = null,
|
||||||
nip94attachments: List<FileHeaderEvent>? = null,
|
nip94attachments: List<FileHeaderEvent>? = null,
|
||||||
|
draftTag: String?,
|
||||||
) {
|
) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
@@ -1533,15 +1583,25 @@ class Account(
|
|||||||
zapRaiserAmount = zapRaiserAmount,
|
zapRaiserAmount = zapRaiserAmount,
|
||||||
geohash = geohash,
|
geohash = geohash,
|
||||||
nip94attachments = nip94attachments,
|
nip94attachments = nip94attachments,
|
||||||
|
isDraft = draftTag != null,
|
||||||
) {
|
) {
|
||||||
Client.send(it, relayList = relayList)
|
if (draftTag != null) {
|
||||||
LocalCache.justConsume(it, null)
|
DraftEvent.create(draftTag, it, signer) { draftEvent ->
|
||||||
|
Client.send(draftEvent, relayList = relayList)
|
||||||
|
LocalCache.justConsume(draftEvent, null)
|
||||||
|
LocalCache.justConsume(it, null)
|
||||||
|
LocalCache.addDraft(draftTag, draftEvent.id(), it.id())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Client.send(it, relayList = relayList)
|
||||||
|
LocalCache.justConsume(it, null)
|
||||||
|
|
||||||
// Rebroadcast replies and tags to the current relay set
|
// Rebroadcast replies and tags to the current relay set
|
||||||
replyTo?.forEach { it.event?.let { Client.send(it, relayList = relayList) } }
|
replyTo?.forEach { it.event?.let { Client.send(it, relayList = relayList) } }
|
||||||
addresses?.forEach {
|
addresses?.forEach {
|
||||||
LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let {
|
LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let {
|
||||||
Client.send(it, relayList = relayList)
|
Client.send(it, relayList = relayList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1557,6 +1617,7 @@ class Account(
|
|||||||
zapRaiserAmount: Long? = null,
|
zapRaiserAmount: Long? = null,
|
||||||
geohash: String? = null,
|
geohash: String? = null,
|
||||||
nip94attachments: List<FileHeaderEvent>? = null,
|
nip94attachments: List<FileHeaderEvent>? = null,
|
||||||
|
draftTag: String?,
|
||||||
) {
|
) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
@@ -1574,9 +1635,19 @@ class Account(
|
|||||||
geohash = geohash,
|
geohash = geohash,
|
||||||
nip94attachments = nip94attachments,
|
nip94attachments = nip94attachments,
|
||||||
signer = signer,
|
signer = signer,
|
||||||
|
isDraft = draftTag != null,
|
||||||
) {
|
) {
|
||||||
Client.send(it)
|
if (draftTag != null) {
|
||||||
LocalCache.justConsume(it, null)
|
DraftEvent.create(draftTag, it, signer) { draftEvent ->
|
||||||
|
Client.send(draftEvent)
|
||||||
|
LocalCache.justConsume(draftEvent, null)
|
||||||
|
LocalCache.justConsume(it, null)
|
||||||
|
LocalCache.addDraft(draftTag, draftEvent.id(), it.id())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Client.send(it)
|
||||||
|
LocalCache.justConsume(it, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1590,6 +1661,7 @@ class Account(
|
|||||||
zapRaiserAmount: Long? = null,
|
zapRaiserAmount: Long? = null,
|
||||||
geohash: String? = null,
|
geohash: String? = null,
|
||||||
nip94attachments: List<FileHeaderEvent>? = null,
|
nip94attachments: List<FileHeaderEvent>? = null,
|
||||||
|
draftTag: String?,
|
||||||
) {
|
) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
@@ -1608,9 +1680,19 @@ class Account(
|
|||||||
geohash = geohash,
|
geohash = geohash,
|
||||||
nip94attachments = nip94attachments,
|
nip94attachments = nip94attachments,
|
||||||
signer = signer,
|
signer = signer,
|
||||||
|
isDraft = draftTag != null,
|
||||||
) {
|
) {
|
||||||
Client.send(it)
|
if (draftTag != null) {
|
||||||
LocalCache.justConsume(it, null)
|
DraftEvent.create(draftTag, it, signer) { draftEvent ->
|
||||||
|
Client.send(draftEvent)
|
||||||
|
LocalCache.justConsume(draftEvent, null)
|
||||||
|
LocalCache.justConsume(it, null)
|
||||||
|
LocalCache.addDraft(draftTag, draftEvent.id(), it.id())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Client.send(it)
|
||||||
|
LocalCache.justConsume(it, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1624,6 +1706,7 @@ class Account(
|
|||||||
zapRaiserAmount: Long? = null,
|
zapRaiserAmount: Long? = null,
|
||||||
geohash: String? = null,
|
geohash: String? = null,
|
||||||
nip94attachments: List<FileHeaderEvent>? = null,
|
nip94attachments: List<FileHeaderEvent>? = null,
|
||||||
|
draftTag: String?,
|
||||||
) {
|
) {
|
||||||
sendPrivateMessage(
|
sendPrivateMessage(
|
||||||
message,
|
message,
|
||||||
@@ -1635,6 +1718,7 @@ class Account(
|
|||||||
zapRaiserAmount,
|
zapRaiserAmount,
|
||||||
geohash,
|
geohash,
|
||||||
nip94attachments,
|
nip94attachments,
|
||||||
|
draftTag,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1648,6 +1732,7 @@ class Account(
|
|||||||
zapRaiserAmount: Long? = null,
|
zapRaiserAmount: Long? = null,
|
||||||
geohash: String? = null,
|
geohash: String? = null,
|
||||||
nip94attachments: List<FileHeaderEvent>? = null,
|
nip94attachments: List<FileHeaderEvent>? = null,
|
||||||
|
draftTag: String?,
|
||||||
) {
|
) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
@@ -1667,9 +1752,19 @@ class Account(
|
|||||||
nip94attachments = nip94attachments,
|
nip94attachments = nip94attachments,
|
||||||
signer = signer,
|
signer = signer,
|
||||||
advertiseNip18 = false,
|
advertiseNip18 = false,
|
||||||
|
isDraft = draftTag != null,
|
||||||
) {
|
) {
|
||||||
Client.send(it)
|
if (draftTag != null) {
|
||||||
LocalCache.consume(it, null)
|
DraftEvent.create(draftTag, it, signer) { draftEvent ->
|
||||||
|
Client.send(draftEvent)
|
||||||
|
LocalCache.justConsume(draftEvent, null)
|
||||||
|
LocalCache.justConsume(it, null)
|
||||||
|
LocalCache.addDraft(draftTag, draftEvent.id(), it.id())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Client.send(it)
|
||||||
|
LocalCache.consume(it, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1684,6 +1779,7 @@ class Account(
|
|||||||
zapRaiserAmount: Long? = null,
|
zapRaiserAmount: Long? = null,
|
||||||
geohash: String? = null,
|
geohash: String? = null,
|
||||||
nip94attachments: List<FileHeaderEvent>? = null,
|
nip94attachments: List<FileHeaderEvent>? = null,
|
||||||
|
draftTag: String? = null,
|
||||||
) {
|
) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
@@ -1701,9 +1797,19 @@ class Account(
|
|||||||
zapRaiserAmount = zapRaiserAmount,
|
zapRaiserAmount = zapRaiserAmount,
|
||||||
geohash = geohash,
|
geohash = geohash,
|
||||||
nip94attachments = nip94attachments,
|
nip94attachments = nip94attachments,
|
||||||
|
draftTag = draftTag,
|
||||||
signer = signer,
|
signer = signer,
|
||||||
) {
|
) {
|
||||||
broadcastPrivately(it)
|
if (draftTag != null) {
|
||||||
|
DraftEvent.create(draftTag, it.msg, signer) { draftEvent ->
|
||||||
|
Client.send(draftEvent)
|
||||||
|
LocalCache.justConsume(draftEvent, null)
|
||||||
|
LocalCache.justConsume(it.msg, null)
|
||||||
|
LocalCache.addDraft(draftTag, draftEvent.id(), it.msg.id())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
broadcastPrivately(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1851,6 +1957,7 @@ class Account(
|
|||||||
isPrivate: Boolean,
|
isPrivate: Boolean,
|
||||||
) {
|
) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
if (note.isDraft()) return
|
||||||
|
|
||||||
if (note is AddressableNote) {
|
if (note is AddressableNote) {
|
||||||
BookmarkListEvent.addReplaceable(
|
BookmarkListEvent.addReplaceable(
|
||||||
@@ -2218,6 +2325,7 @@ class Account(
|
|||||||
|
|
||||||
fun cachedDecryptContent(note: Note): String? {
|
fun cachedDecryptContent(note: Note): String? {
|
||||||
val event = note.event
|
val event = note.event
|
||||||
|
|
||||||
return if (event is PrivateDmEvent && isWriteable()) {
|
return if (event is PrivateDmEvent && isWriteable()) {
|
||||||
event.cachedContentFor(signer)
|
event.cachedContentFor(signer)
|
||||||
} else if (event is LnZapRequestEvent && event.isPrivateZap() && isWriteable()) {
|
} else if (event is LnZapRequestEvent && event.isPrivateZap() && isWriteable()) {
|
||||||
|
|||||||
23
app/src/main/java/com/vitorpamplona/amethyst/model/Drafts.kt
Normal file
23
app/src/main/java/com/vitorpamplona/amethyst/model/Drafts.kt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* 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.model
|
||||||
|
|
||||||
|
data class Drafts(val mainId: String, val eventId: String)
|
||||||
@@ -62,6 +62,7 @@ import com.vitorpamplona.quartz.events.CommunityListEvent
|
|||||||
import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
|
import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
|
||||||
import com.vitorpamplona.quartz.events.ContactListEvent
|
import com.vitorpamplona.quartz.events.ContactListEvent
|
||||||
import com.vitorpamplona.quartz.events.DeletionEvent
|
import com.vitorpamplona.quartz.events.DeletionEvent
|
||||||
|
import com.vitorpamplona.quartz.events.DraftEvent
|
||||||
import com.vitorpamplona.quartz.events.EmojiPackEvent
|
import com.vitorpamplona.quartz.events.EmojiPackEvent
|
||||||
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
|
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
|
||||||
import com.vitorpamplona.quartz.events.Event
|
import com.vitorpamplona.quartz.events.Event
|
||||||
@@ -128,7 +129,7 @@ object LocalCache {
|
|||||||
val users = LargeCache<HexKey, User>()
|
val users = LargeCache<HexKey, User>()
|
||||||
val notes = LargeCache<HexKey, Note>()
|
val notes = LargeCache<HexKey, Note>()
|
||||||
val addressables = LargeCache<String, AddressableNote>()
|
val addressables = LargeCache<String, AddressableNote>()
|
||||||
|
val drafts = ConcurrentHashMap<String, MutableList<Drafts>>()
|
||||||
val channels = LargeCache<HexKey, Channel>()
|
val channels = LargeCache<HexKey, Channel>()
|
||||||
val awaitingPaymentRequests = ConcurrentHashMap<HexKey, Pair<Note?, (LnZapPaymentResponseEvent) -> Unit>>(10)
|
val awaitingPaymentRequests = ConcurrentHashMap<HexKey, Pair<Note?, (LnZapPaymentResponseEvent) -> Unit>>(10)
|
||||||
|
|
||||||
@@ -141,6 +142,34 @@ object LocalCache {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun draftNotes(draftTag: String): List<Note> {
|
||||||
|
return drafts[draftTag]?.mapNotNull {
|
||||||
|
getNoteIfExists(it.mainId)
|
||||||
|
} ?: listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDrafts(eventId: String): List<Note> {
|
||||||
|
return drafts.filter {
|
||||||
|
it.value.any { it.eventId == eventId }
|
||||||
|
}.values.map {
|
||||||
|
it.mapNotNull {
|
||||||
|
checkGetOrCreateNote(it.mainId)
|
||||||
|
}
|
||||||
|
}.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addDraft(
|
||||||
|
key: String,
|
||||||
|
mainId: String,
|
||||||
|
draftId: String,
|
||||||
|
) {
|
||||||
|
val data = drafts[key] ?: mutableListOf()
|
||||||
|
if (data.none { it.mainId == mainId }) {
|
||||||
|
data.add(Drafts(mainId, draftId))
|
||||||
|
drafts[key] = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getOrCreateUser(key: HexKey): User {
|
fun getOrCreateUser(key: HexKey): User {
|
||||||
// checkNotInMainThread()
|
// checkNotInMainThread()
|
||||||
require(isValidHex(key = key)) { "$key is not a valid hex" }
|
require(isValidHex(key = key)) { "$key is not a valid hex" }
|
||||||
@@ -2013,6 +2042,13 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun consume(
|
||||||
|
event: DraftEvent,
|
||||||
|
relay: Relay?,
|
||||||
|
) {
|
||||||
|
consumeBaseReplaceable(event, relay)
|
||||||
|
}
|
||||||
|
|
||||||
fun justConsume(
|
fun justConsume(
|
||||||
event: Event,
|
event: Event,
|
||||||
relay: Relay?,
|
relay: Relay?,
|
||||||
@@ -2050,6 +2086,7 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
is ContactListEvent -> consume(event)
|
is ContactListEvent -> consume(event)
|
||||||
is DeletionEvent -> consume(event)
|
is DeletionEvent -> consume(event)
|
||||||
|
is DraftEvent -> consume(event, relay)
|
||||||
is EmojiPackEvent -> consume(event, relay)
|
is EmojiPackEvent -> consume(event, relay)
|
||||||
is EmojiPackSelectionEvent -> consume(event, relay)
|
is EmojiPackSelectionEvent -> consume(event, relay)
|
||||||
is SealedGossipEvent -> consume(event, relay)
|
is SealedGossipEvent -> consume(event, relay)
|
||||||
|
|||||||
@@ -184,6 +184,13 @@ open class Note(val idHex: String) {
|
|||||||
|
|
||||||
open fun createdAt() = event?.createdAt()
|
open fun createdAt() = event?.createdAt()
|
||||||
|
|
||||||
|
fun isDraft(): Boolean {
|
||||||
|
event?.let {
|
||||||
|
return it.sig().isBlank()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
fun loadEvent(
|
fun loadEvent(
|
||||||
event: Event,
|
event: Event,
|
||||||
author: User,
|
author: User,
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import com.vitorpamplona.quartz.events.CalendarRSVPEvent
|
|||||||
import com.vitorpamplona.quartz.events.CalendarTimeSlotEvent
|
import com.vitorpamplona.quartz.events.CalendarTimeSlotEvent
|
||||||
import com.vitorpamplona.quartz.events.ChannelMessageEvent
|
import com.vitorpamplona.quartz.events.ChannelMessageEvent
|
||||||
import com.vitorpamplona.quartz.events.ContactListEvent
|
import com.vitorpamplona.quartz.events.ContactListEvent
|
||||||
|
import com.vitorpamplona.quartz.events.DraftEvent
|
||||||
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
|
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
|
||||||
import com.vitorpamplona.quartz.events.Event
|
import com.vitorpamplona.quartz.events.Event
|
||||||
import com.vitorpamplona.quartz.events.EventInterface
|
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() =
|
fun createGiftWrapsToMeFilter() =
|
||||||
TypedFilter(
|
TypedFilter(
|
||||||
types = COMMON_FEED_TYPES,
|
types = COMMON_FEED_TYPES,
|
||||||
@@ -262,22 +273,46 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
|
|||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
|
|
||||||
if (LocalCache.justVerify(event)) {
|
if (LocalCache.justVerify(event)) {
|
||||||
if (event is GiftWrapEvent) {
|
when (event) {
|
||||||
// Avoid decrypting over and over again if the event already exist.
|
is DraftEvent -> {
|
||||||
val note = LocalCache.getNoteIfExists(event.id)
|
// Avoid decrypting over and over again if the event already exist.
|
||||||
if (note != null && relay.brief in note.relays) return
|
|
||||||
|
|
||||||
event.cachedGift(account.signer) { this.consume(it, relay) }
|
val note = LocalCache.getNoteIfExists(event.id)
|
||||||
}
|
if (note != null && relay.brief in note.relays) return
|
||||||
|
|
||||||
if (event is SealedGossipEvent) {
|
LocalCache.justConsume(event, relay)
|
||||||
// Avoid decrypting over and over again if the event already exist.
|
event.plainContent(account.signer) {
|
||||||
val note = LocalCache.getNoteIfExists(event.id)
|
val tag =
|
||||||
if (note != null && relay.brief in note.relays) return
|
event.tags().filter { it.size > 1 && it[0] == "d" }.map {
|
||||||
|
it[1]
|
||||||
|
}.firstOrNull()
|
||||||
|
|
||||||
event.cachedGossip(account.signer) { LocalCache.justConsume(it, relay) }
|
LocalCache.justConsume(it, relay)
|
||||||
} else {
|
tag?.let { lTag ->
|
||||||
LocalCache.justConsume(event, relay)
|
LocalCache.addDraft(lTag, event.id(), it.id())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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 +363,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
|
|||||||
createAccountSettingsFilter(),
|
createAccountSettingsFilter(),
|
||||||
createAccountLastPostsListFilter(),
|
createAccountLastPostsListFilter(),
|
||||||
createOtherAccountsBaseFilter(),
|
createOtherAccountsBaseFilter(),
|
||||||
|
createDraftsFilter(),
|
||||||
)
|
)
|
||||||
.ifEmpty { null }
|
.ifEmpty { null }
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -34,8 +34,11 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPollOption(
|
fun NewPollOption(
|
||||||
@@ -45,7 +48,12 @@ fun NewPollOption(
|
|||||||
Row {
|
Row {
|
||||||
val deleteIcon: @Composable (() -> Unit) = {
|
val deleteIcon: @Composable (() -> Unit) = {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { pollViewModel.pollOptions.remove(optionIndex) },
|
onClick = {
|
||||||
|
pollViewModel.pollOptions.remove(optionIndex)
|
||||||
|
pollViewModel.viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
pollViewModel.saveDraft()
|
||||||
|
}
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Delete,
|
imageVector = Icons.Default.Delete,
|
||||||
@@ -57,7 +65,12 @@ fun NewPollOption(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier.weight(1F),
|
modifier = Modifier.weight(1F),
|
||||||
value = pollViewModel.pollOptions[optionIndex] ?: "",
|
value = pollViewModel.pollOptions[optionIndex] ?: "",
|
||||||
onValueChange = { pollViewModel.pollOptions[optionIndex] = it },
|
onValueChange = {
|
||||||
|
pollViewModel.pollOptions[optionIndex] = it
|
||||||
|
pollViewModel.viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
pollViewModel.saveDraft()
|
||||||
|
}
|
||||||
|
},
|
||||||
label = {
|
label = {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.poll_option_index).format(optionIndex + 1),
|
text = stringResource(R.string.poll_option_index).format(optionIndex + 1),
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
@@ -171,13 +172,18 @@ import kotlinx.collections.immutable.ImmutableList
|
|||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.lang.Math.round
|
import java.lang.Math.round
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPostView(
|
fun NewPostView(
|
||||||
onClose: () -> Unit,
|
onClose: () -> Unit,
|
||||||
@@ -185,6 +191,7 @@ fun NewPostView(
|
|||||||
quote: Note? = null,
|
quote: Note? = null,
|
||||||
fork: Note? = null,
|
fork: Note? = null,
|
||||||
version: Note? = null,
|
version: Note? = null,
|
||||||
|
draft: Note? = null,
|
||||||
enableMessageInterface: Boolean = false,
|
enableMessageInterface: Boolean = false,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
@@ -200,9 +207,17 @@ fun NewPostView(
|
|||||||
var relayList = remember { accountViewModel.account.activeWriteRelays().toImmutableList() }
|
var relayList = remember { accountViewModel.account.activeWriteRelays().toImmutableList() }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
postViewModel.load(accountViewModel, baseReplyTo, quote, fork, version)
|
|
||||||
|
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
|
postViewModel.draftTextChanges
|
||||||
|
.receiveAsFlow()
|
||||||
|
.debounce(1000)
|
||||||
|
.collectLatest {
|
||||||
|
postViewModel.sendPost(relayList = relayList, localDraft = postViewModel.draftTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
postViewModel.load(accountViewModel, baseReplyTo, quote, fork, version, draft)
|
||||||
|
|
||||||
postViewModel.imageUploadingError.collect { error ->
|
postViewModel.imageUploadingError.collect { error ->
|
||||||
withContext(Dispatchers.Main) { Toast.makeText(context, error, Toast.LENGTH_SHORT).show() }
|
withContext(Dispatchers.Main) { Toast.makeText(context, error, Toast.LENGTH_SHORT).show() }
|
||||||
}
|
}
|
||||||
@@ -582,6 +597,9 @@ private fun BottomRowActions(postViewModel: NewPostViewModel) {
|
|||||||
|
|
||||||
MarkAsSensitive(postViewModel) {
|
MarkAsSensitive(postViewModel) {
|
||||||
postViewModel.wantsToMarkAsSensitive = !postViewModel.wantsToMarkAsSensitive
|
postViewModel.wantsToMarkAsSensitive = !postViewModel.wantsToMarkAsSensitive
|
||||||
|
postViewModel.viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
postViewModel.saveDraft()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AddGeoHash(postViewModel) {
|
AddGeoHash(postViewModel) {
|
||||||
@@ -827,7 +845,12 @@ fun SellProduct(postViewModel: NewPostViewModel) {
|
|||||||
|
|
||||||
MyTextField(
|
MyTextField(
|
||||||
value = postViewModel.title,
|
value = postViewModel.title,
|
||||||
onValueChange = { postViewModel.title = it },
|
onValueChange = {
|
||||||
|
postViewModel.title = it
|
||||||
|
postViewModel.viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
postViewModel.saveDraft()
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
placeholder = {
|
placeholder = {
|
||||||
Text(
|
Text(
|
||||||
@@ -870,6 +893,9 @@ fun SellProduct(postViewModel: NewPostViewModel) {
|
|||||||
postViewModel.price = it
|
postViewModel.price = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
postViewModel.viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
postViewModel.saveDraft()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
placeholder = {
|
placeholder = {
|
||||||
Text(
|
Text(
|
||||||
@@ -934,7 +960,12 @@ fun SellProduct(postViewModel: NewPostViewModel) {
|
|||||||
TextSpinner(
|
TextSpinner(
|
||||||
placeholder = conditionTypes.filter { it.first == postViewModel.condition }.first().second,
|
placeholder = conditionTypes.filter { it.first == postViewModel.condition }.first().second,
|
||||||
options = conditionOptions,
|
options = conditionOptions,
|
||||||
onSelect = { postViewModel.condition = conditionTypes[it].first },
|
onSelect = {
|
||||||
|
postViewModel.condition = conditionTypes[it].first
|
||||||
|
postViewModel.viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
postViewModel.saveDraft()
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
@@ -998,7 +1029,12 @@ fun SellProduct(postViewModel: NewPostViewModel) {
|
|||||||
categoryTypes.filter { it.second == postViewModel.category.text }.firstOrNull()?.second
|
categoryTypes.filter { it.second == postViewModel.category.text }.firstOrNull()?.second
|
||||||
?: "",
|
?: "",
|
||||||
options = categoryOptions,
|
options = categoryOptions,
|
||||||
onSelect = { postViewModel.category = TextFieldValue(categoryTypes[it].second) },
|
onSelect = {
|
||||||
|
postViewModel.category = TextFieldValue(categoryTypes[it].second)
|
||||||
|
postViewModel.viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
postViewModel.saveDraft()
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
@@ -1033,7 +1069,12 @@ fun SellProduct(postViewModel: NewPostViewModel) {
|
|||||||
|
|
||||||
MyTextField(
|
MyTextField(
|
||||||
value = postViewModel.locationText,
|
value = postViewModel.locationText,
|
||||||
onValueChange = { postViewModel.locationText = it },
|
onValueChange = {
|
||||||
|
postViewModel.locationText = it
|
||||||
|
postViewModel.viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
postViewModel.saveDraft()
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
placeholder = {
|
placeholder = {
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -69,10 +69,12 @@ import kotlinx.coroutines.CancellationException
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
enum class UserSuggestionAnchor {
|
enum class UserSuggestionAnchor {
|
||||||
MAIN_MESSAGE,
|
MAIN_MESSAGE,
|
||||||
@@ -82,6 +84,7 @@ enum class UserSuggestionAnchor {
|
|||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
open class NewPostViewModel() : ViewModel() {
|
open class NewPostViewModel() : ViewModel() {
|
||||||
|
var draftTag: String = UUID.randomUUID().toString()
|
||||||
var accountViewModel: AccountViewModel? = null
|
var accountViewModel: AccountViewModel? = null
|
||||||
var account: Account? = null
|
var account: Account? = null
|
||||||
var requiresNIP24: Boolean = false
|
var requiresNIP24: Boolean = false
|
||||||
@@ -164,6 +167,8 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
// NIP24 Wrapped DMs / Group messages
|
// NIP24 Wrapped DMs / Group messages
|
||||||
var nip24 by mutableStateOf(false)
|
var nip24 by mutableStateOf(false)
|
||||||
|
|
||||||
|
val draftTextChanges = Channel<String>(Channel.CONFLATED)
|
||||||
|
|
||||||
fun lnAddress(): String? {
|
fun lnAddress(): String? {
|
||||||
return account?.userProfile()?.info?.lnAddress()
|
return account?.userProfile()?.info?.lnAddress()
|
||||||
}
|
}
|
||||||
@@ -182,127 +187,236 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
quote: Note?,
|
quote: Note?,
|
||||||
fork: Note?,
|
fork: Note?,
|
||||||
version: Note?,
|
version: Note?,
|
||||||
|
draft: Note?,
|
||||||
) {
|
) {
|
||||||
this.accountViewModel = accountViewModel
|
this.accountViewModel = accountViewModel
|
||||||
this.account = accountViewModel.account
|
this.account = accountViewModel.account
|
||||||
|
|
||||||
originalNote = replyingTo
|
if (draft != null) {
|
||||||
replyingTo?.let { replyNote ->
|
loadFromDraft(draft, accountViewModel)
|
||||||
if (replyNote.event is BaseTextNoteEvent) {
|
} else {
|
||||||
this.eTags = (replyNote.replyTo ?: emptyList()).plus(replyNote)
|
originalNote = replyingTo
|
||||||
} else {
|
replyingTo?.let { replyNote ->
|
||||||
this.eTags = listOf(replyNote)
|
if (replyNote.event is BaseTextNoteEvent) {
|
||||||
}
|
this.eTags = (replyNote.replyTo ?: emptyList()).plus(replyNote)
|
||||||
|
} else {
|
||||||
|
this.eTags = listOf(replyNote)
|
||||||
|
}
|
||||||
|
|
||||||
if (replyNote.event !is CommunityDefinitionEvent) {
|
if (replyNote.event !is CommunityDefinitionEvent) {
|
||||||
replyNote.author?.let { replyUser ->
|
replyNote.author?.let { replyUser ->
|
||||||
val currentMentions =
|
val currentMentions =
|
||||||
(replyNote.event as? TextNoteEvent)?.mentions()?.map { LocalCache.getOrCreateUser(it) }
|
(replyNote.event as? TextNoteEvent)?.mentions()?.map { LocalCache.getOrCreateUser(it) }
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
|
|
||||||
if (currentMentions.contains(replyUser)) {
|
if (currentMentions.contains(replyUser)) {
|
||||||
this.pTags = currentMentions
|
this.pTags = currentMentions
|
||||||
} else {
|
} else {
|
||||||
this.pTags = currentMentions.plus(replyUser)
|
this.pTags = currentMentions.plus(replyUser)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
?: run {
|
||||||
?: run {
|
eTags = null
|
||||||
eTags = null
|
pTags = null
|
||||||
pTags = null
|
}
|
||||||
|
|
||||||
|
canAddInvoice = accountViewModel.userProfile().info?.lnAddress() != null
|
||||||
|
canAddZapRaiser = accountViewModel.userProfile().info?.lnAddress() != null
|
||||||
|
canUsePoll = originalNote?.event !is PrivateDmEvent && originalNote?.channelHex() == null
|
||||||
|
contentToAddUrl = null
|
||||||
|
|
||||||
|
wantsForwardZapTo = false
|
||||||
|
wantsToMarkAsSensitive = false
|
||||||
|
wantsToAddGeoHash = false
|
||||||
|
wantsZapraiser = false
|
||||||
|
zapRaiserAmount = null
|
||||||
|
forwardZapTo = Split()
|
||||||
|
forwardZapToEditting = TextFieldValue("")
|
||||||
|
|
||||||
|
quote?.let {
|
||||||
|
message = TextFieldValue(message.text + "\nnostr:${it.toNEvent()}")
|
||||||
|
urlPreview = findUrlInMessage()
|
||||||
|
|
||||||
|
it.author?.let { quotedUser ->
|
||||||
|
if (quotedUser.pubkeyHex != accountViewModel.userProfile().pubkeyHex) {
|
||||||
|
if (forwardZapTo.items.none { it.key.pubkeyHex == quotedUser.pubkeyHex }) {
|
||||||
|
forwardZapTo.addItem(quotedUser)
|
||||||
|
}
|
||||||
|
if (forwardZapTo.items.none { it.key.pubkeyHex == accountViewModel.userProfile().pubkeyHex }) {
|
||||||
|
forwardZapTo.addItem(accountViewModel.userProfile())
|
||||||
|
}
|
||||||
|
|
||||||
|
val pos = forwardZapTo.items.indexOfFirst { it.key.pubkeyHex == quotedUser.pubkeyHex }
|
||||||
|
forwardZapTo.updatePercentage(pos, 0.9f)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fork?.let {
|
||||||
|
message = TextFieldValue(version?.event?.content() ?: it.event?.content() ?: "")
|
||||||
|
urlPreview = findUrlInMessage()
|
||||||
|
|
||||||
|
it.event?.isSensitive()?.let {
|
||||||
|
if (it) wantsToMarkAsSensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
it.event?.zapraiserAmount()?.let {
|
||||||
|
zapRaiserAmount = it
|
||||||
|
}
|
||||||
|
|
||||||
|
it.event?.zapSplitSetup()?.let {
|
||||||
|
val totalWeight = it.sumOf { if (it.isLnAddress) 0.0 else it.weight }
|
||||||
|
|
||||||
|
it.forEach {
|
||||||
|
if (!it.isLnAddress) {
|
||||||
|
forwardZapTo.addItem(LocalCache.getOrCreateUser(it.lnAddressOrPubKeyHex), (it.weight / totalWeight).toFloat())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only adds if it is not already set up.
|
||||||
|
if (forwardZapTo.items.isEmpty()) {
|
||||||
|
it.author?.let { forkedAuthor ->
|
||||||
|
if (forkedAuthor.pubkeyHex != accountViewModel.userProfile().pubkeyHex) {
|
||||||
|
if (forwardZapTo.items.none { it.key.pubkeyHex == forkedAuthor.pubkeyHex }) forwardZapTo.addItem(forkedAuthor)
|
||||||
|
if (forwardZapTo.items.none { it.key.pubkeyHex == accountViewModel.userProfile().pubkeyHex }) forwardZapTo.addItem(accountViewModel.userProfile())
|
||||||
|
|
||||||
|
val pos = forwardZapTo.items.indexOfFirst { it.key.pubkeyHex == forkedAuthor.pubkeyHex }
|
||||||
|
forwardZapTo.updatePercentage(pos, 0.8f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it.author?.let {
|
||||||
|
if (this.pTags == null) {
|
||||||
|
this.pTags = listOf(it)
|
||||||
|
} else if (this.pTags?.contains(it) != true) {
|
||||||
|
this.pTags = listOf(it) + (this.pTags ?: emptyList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forkedFromNote = it
|
||||||
|
} ?: run {
|
||||||
|
forkedFromNote = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!forwardZapTo.items.isEmpty()) {
|
||||||
|
wantsForwardZapTo = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadFromDraft(
|
||||||
|
draft: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
) {
|
||||||
|
Log.d("draft", draft.event!!.toJson())
|
||||||
|
|
||||||
|
draftTag = LocalCache.drafts.filter {
|
||||||
|
it.value.any { it.eventId == draft.event?.id() }
|
||||||
|
}.keys.firstOrNull() ?: draftTag
|
||||||
|
|
||||||
canAddInvoice = accountViewModel.userProfile().info?.lnAddress() != null
|
canAddInvoice = accountViewModel.userProfile().info?.lnAddress() != null
|
||||||
canAddZapRaiser = accountViewModel.userProfile().info?.lnAddress() != null
|
canAddZapRaiser = accountViewModel.userProfile().info?.lnAddress() != null
|
||||||
canUsePoll = originalNote?.event !is PrivateDmEvent && originalNote?.channelHex() == null
|
|
||||||
contentToAddUrl = null
|
contentToAddUrl = null
|
||||||
|
|
||||||
wantsForwardZapTo = false
|
val localfowardZapTo = draft.event?.tags()?.filter { it.size > 1 && it[0] == "zap" } ?: listOf()
|
||||||
wantsToMarkAsSensitive = false
|
|
||||||
wantsToAddGeoHash = false
|
|
||||||
wantsZapraiser = false
|
|
||||||
zapRaiserAmount = null
|
|
||||||
forwardZapTo = Split()
|
forwardZapTo = Split()
|
||||||
|
localfowardZapTo.forEach {
|
||||||
|
val user = LocalCache.getOrCreateUser(it[1])
|
||||||
|
val value = it.last().toFloatOrNull() ?: 0f
|
||||||
|
forwardZapTo.addItem(user, value)
|
||||||
|
}
|
||||||
forwardZapToEditting = TextFieldValue("")
|
forwardZapToEditting = TextFieldValue("")
|
||||||
|
wantsForwardZapTo = localfowardZapTo.isNotEmpty()
|
||||||
|
|
||||||
quote?.let {
|
wantsToMarkAsSensitive = draft.event?.tags()?.any { it.size > 1 && it[0] == "content-warning" } ?: false
|
||||||
message = TextFieldValue(message.text + "\nnostr:${it.toNEvent()}")
|
wantsToAddGeoHash = draft.event?.tags()?.any { it.size > 1 && it[0] == "g" } ?: false
|
||||||
urlPreview = findUrlInMessage()
|
val zapraiser = draft.event?.tags()?.filter { it.size > 1 && it[0] == "zapraiser" } ?: listOf()
|
||||||
|
wantsZapraiser = zapraiser.isNotEmpty()
|
||||||
it.author?.let { quotedUser ->
|
zapRaiserAmount = null
|
||||||
if (quotedUser.pubkeyHex != accountViewModel.userProfile().pubkeyHex) {
|
if (wantsZapraiser) {
|
||||||
if (forwardZapTo.items.none { it.key.pubkeyHex == quotedUser.pubkeyHex }) {
|
zapRaiserAmount = zapraiser.first()[1].toLongOrNull() ?: 0
|
||||||
forwardZapTo.addItem(quotedUser)
|
|
||||||
}
|
|
||||||
if (forwardZapTo.items.none { it.key.pubkeyHex == accountViewModel.userProfile().pubkeyHex }) {
|
|
||||||
forwardZapTo.addItem(accountViewModel.userProfile())
|
|
||||||
}
|
|
||||||
|
|
||||||
val pos = forwardZapTo.items.indexOfFirst { it.key.pubkeyHex == quotedUser.pubkeyHex }
|
|
||||||
forwardZapTo.updatePercentage(pos, 0.9f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fork?.let {
|
eTags =
|
||||||
message = TextFieldValue(version?.event?.content() ?: it.event?.content() ?: "")
|
draft.event?.tags()?.filter { it.size > 1 && (it[0] == "e" || it[0] == "a") && it.getOrNull(3) != "fork" }?.mapNotNull {
|
||||||
urlPreview = findUrlInMessage()
|
val note = LocalCache.checkGetOrCreateNote(it[1])
|
||||||
|
note
|
||||||
it.event?.isSensitive()?.let {
|
|
||||||
if (it) wantsToMarkAsSensitive = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
it.event?.zapraiserAmount()?.let {
|
pTags =
|
||||||
zapRaiserAmount = it
|
draft.event?.tags()?.filter { it.size > 1 && it[0] == "p" }?.map {
|
||||||
|
LocalCache.getOrCreateUser(it[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
it.event?.zapSplitSetup()?.let {
|
draft.event?.tags()?.filter { it.size > 1 && (it[0] == "e" || it[0] == "a") && it.getOrNull(3) == "fork" }?.forEach {
|
||||||
val totalWeight = it.sumOf { if (it.isLnAddress) 0.0 else it.weight }
|
val note = LocalCache.checkGetOrCreateNote(it[1])
|
||||||
|
forkedFromNote = note
|
||||||
it.forEach {
|
|
||||||
if (!it.isLnAddress) {
|
|
||||||
forwardZapTo.addItem(LocalCache.getOrCreateUser(it.lnAddressOrPubKeyHex), (it.weight / totalWeight).toFloat())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only adds if it is not already set up.
|
|
||||||
if (forwardZapTo.items.isEmpty()) {
|
|
||||||
it.author?.let { forkedAuthor ->
|
|
||||||
if (forkedAuthor.pubkeyHex != accountViewModel.userProfile().pubkeyHex) {
|
|
||||||
if (forwardZapTo.items.none { it.key.pubkeyHex == forkedAuthor.pubkeyHex }) forwardZapTo.addItem(forkedAuthor)
|
|
||||||
if (forwardZapTo.items.none { it.key.pubkeyHex == accountViewModel.userProfile().pubkeyHex }) forwardZapTo.addItem(accountViewModel.userProfile())
|
|
||||||
|
|
||||||
val pos = forwardZapTo.items.indexOfFirst { it.key.pubkeyHex == forkedAuthor.pubkeyHex }
|
|
||||||
forwardZapTo.updatePercentage(pos, 0.8f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
it.author?.let {
|
|
||||||
if (this.pTags == null) {
|
|
||||||
this.pTags = listOf(it)
|
|
||||||
} else if (this.pTags?.contains(it) != true) {
|
|
||||||
this.pTags = listOf(it) + (this.pTags ?: emptyList())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
forkedFromNote = it
|
|
||||||
} ?: run {
|
|
||||||
forkedFromNote = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!forwardZapTo.items.isEmpty()) {
|
originalNote =
|
||||||
|
draft.event?.tags()?.filter { it.size > 1 && (it[0] == "e" || it[0] == "a") && it.getOrNull(3) == "root" }?.map {
|
||||||
|
LocalCache.checkGetOrCreateNote(it[1])
|
||||||
|
}?.firstOrNull()
|
||||||
|
|
||||||
|
canUsePoll = originalNote?.event !is PrivateDmEvent && originalNote?.channelHex() == null
|
||||||
|
|
||||||
|
if (forwardZapTo.items.isNotEmpty()) {
|
||||||
wantsForwardZapTo = true
|
wantsForwardZapTo = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val polls = draft.event?.tags()?.filter { it.size > 1 && it[0] == "poll_option" } ?: emptyList()
|
||||||
|
wantsPoll = polls.isNotEmpty()
|
||||||
|
|
||||||
|
polls.forEach {
|
||||||
|
pollOptions[it[1].toInt()] = it[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
val minMax = draft.event?.tags()?.filter { it.size > 1 && (it[0] == "value_minimum" || it[0] == "value_maximum") } ?: listOf()
|
||||||
|
minMax.forEach {
|
||||||
|
if (it[0] == "value_maximum") {
|
||||||
|
valueMaximum = it[1].toInt()
|
||||||
|
} else if (it[0] == "value_minimum") {
|
||||||
|
valueMinimum = it[1].toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wantsProduct = draft.event?.kind() == 30402
|
||||||
|
|
||||||
|
title = TextFieldValue(draft.event?.tags()?.filter { it.size > 1 && it[0] == "title" }?.map { it[1] }?.firstOrNull() ?: "")
|
||||||
|
price = TextFieldValue(draft.event?.tags()?.filter { it.size > 1 && it[0] == "price" }?.map { it[1] }?.firstOrNull() ?: "")
|
||||||
|
category = TextFieldValue(draft.event?.tags()?.filter { it.size > 1 && it[0] == "t" }?.map { it[1] }?.firstOrNull() ?: "")
|
||||||
|
locationText = TextFieldValue(draft.event?.tags()?.filter { it.size > 1 && it[0] == "location" }?.map { it[1] }?.firstOrNull() ?: "")
|
||||||
|
condition = ClassifiedsEvent.CONDITION.entries.firstOrNull {
|
||||||
|
it.value == draft.event?.tags()?.filter { it.size > 1 && it[0] == "condition" }?.map { it[1] }?.firstOrNull()
|
||||||
|
} ?: ClassifiedsEvent.CONDITION.USED_LIKE_NEW
|
||||||
|
|
||||||
|
message =
|
||||||
|
if (draft.event is PrivateDmEvent) {
|
||||||
|
val event = draft.event as PrivateDmEvent
|
||||||
|
TextFieldValue(event.cachedContentFor(accountViewModel.account.signer) ?: "")
|
||||||
|
} else {
|
||||||
|
TextFieldValue(draft.event?.content() ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
nip24 = draft.event is ChatMessageEvent
|
||||||
|
urlPreview = findUrlInMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendPost(relayList: List<Relay>? = null) {
|
fun sendPost(
|
||||||
viewModelScope.launch(Dispatchers.IO) { innerSendPost(relayList) }
|
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) {
|
if (accountViewModel == null) {
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return
|
||||||
@@ -363,6 +477,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
zapRaiserAmount = localZapRaiserAmount,
|
zapRaiserAmount = localZapRaiserAmount,
|
||||||
geohash = geoHash,
|
geohash = geoHash,
|
||||||
nip94attachments = usedAttachments,
|
nip94attachments = usedAttachments,
|
||||||
|
draftTag = localDraft,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
account?.sendChannelMessage(
|
account?.sendChannelMessage(
|
||||||
@@ -375,6 +490,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
zapRaiserAmount = localZapRaiserAmount,
|
zapRaiserAmount = localZapRaiserAmount,
|
||||||
geohash = geoHash,
|
geohash = geoHash,
|
||||||
nip94attachments = usedAttachments,
|
nip94attachments = usedAttachments,
|
||||||
|
draftTag = localDraft,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (originalNote?.event is PrivateDmEvent) {
|
} else if (originalNote?.event is PrivateDmEvent) {
|
||||||
@@ -388,6 +504,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
zapRaiserAmount = localZapRaiserAmount,
|
zapRaiserAmount = localZapRaiserAmount,
|
||||||
geohash = geoHash,
|
geohash = geoHash,
|
||||||
nip94attachments = usedAttachments,
|
nip94attachments = usedAttachments,
|
||||||
|
draftTag = localDraft,
|
||||||
)
|
)
|
||||||
} else if (originalNote?.event is ChatMessageEvent) {
|
} else if (originalNote?.event is ChatMessageEvent) {
|
||||||
val receivers =
|
val receivers =
|
||||||
@@ -423,6 +540,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
zapRaiserAmount = localZapRaiserAmount,
|
zapRaiserAmount = localZapRaiserAmount,
|
||||||
geohash = geoHash,
|
geohash = geoHash,
|
||||||
nip94attachments = usedAttachments,
|
nip94attachments = usedAttachments,
|
||||||
|
draftTag = localDraft,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
account?.sendPrivateMessage(
|
account?.sendPrivateMessage(
|
||||||
@@ -435,6 +553,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
zapRaiserAmount = localZapRaiserAmount,
|
zapRaiserAmount = localZapRaiserAmount,
|
||||||
geohash = geoHash,
|
geohash = geoHash,
|
||||||
nip94attachments = usedAttachments,
|
nip94attachments = usedAttachments,
|
||||||
|
draftTag = localDraft,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (originalNote?.event is GitIssueEvent) {
|
} else if (originalNote?.event is GitIssueEvent) {
|
||||||
@@ -475,6 +594,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
relayList = relayList,
|
relayList = relayList,
|
||||||
geohash = geoHash,
|
geohash = geoHash,
|
||||||
nip94attachments = usedAttachments,
|
nip94attachments = usedAttachments,
|
||||||
|
draftTag = localDraft,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
if (wantsPoll) {
|
if (wantsPoll) {
|
||||||
@@ -493,6 +613,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
relayList,
|
relayList,
|
||||||
geoHash,
|
geoHash,
|
||||||
nip94attachments = usedAttachments,
|
nip94attachments = usedAttachments,
|
||||||
|
draftTag = localDraft,
|
||||||
)
|
)
|
||||||
} else if (wantsProduct) {
|
} else if (wantsProduct) {
|
||||||
account?.sendClassifieds(
|
account?.sendClassifieds(
|
||||||
@@ -511,6 +632,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
relayList = relayList,
|
relayList = relayList,
|
||||||
geohash = geoHash,
|
geohash = geoHash,
|
||||||
nip94attachments = usedAttachments,
|
nip94attachments = usedAttachments,
|
||||||
|
draftTag = localDraft,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// adds markers
|
// adds markers
|
||||||
@@ -547,11 +669,13 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
relayList = relayList,
|
relayList = relayList,
|
||||||
geohash = geoHash,
|
geohash = geoHash,
|
||||||
nip94attachments = usedAttachments,
|
nip94attachments = usedAttachments,
|
||||||
|
draftTag = localDraft,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (localDraft == null) {
|
||||||
cancel()
|
cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun upload(
|
fun upload(
|
||||||
@@ -635,6 +759,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
urlPreview = null
|
urlPreview = null
|
||||||
isUploadingImage = false
|
isUploadingImage = false
|
||||||
pTags = null
|
pTags = null
|
||||||
|
eTags = null
|
||||||
|
|
||||||
wantsDirectMessage = false
|
wantsDirectMessage = false
|
||||||
|
|
||||||
@@ -663,6 +788,11 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
userSuggestions = emptyList()
|
userSuggestions = emptyList()
|
||||||
userSuggestionAnchor = null
|
userSuggestionAnchor = null
|
||||||
userSuggestionsMainMessage = null
|
userSuggestionsMainMessage = null
|
||||||
|
originalNote = null
|
||||||
|
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
accountViewModel?.deleteDraft(draftTag)
|
||||||
|
}
|
||||||
|
|
||||||
NostrSearchEventOrUserDataSource.clear()
|
NostrSearchEventOrUserDataSource.clear()
|
||||||
}
|
}
|
||||||
@@ -679,6 +809,10 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
pTags = pTags?.filter { it != userToRemove }
|
pTags = pTags?.filter { it != userToRemove }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open suspend fun saveDraft() {
|
||||||
|
draftTextChanges.send("")
|
||||||
|
}
|
||||||
|
|
||||||
open fun updateMessage(it: TextFieldValue) {
|
open fun updateMessage(it: TextFieldValue) {
|
||||||
message = it
|
message = it
|
||||||
urlPreview = findUrlInMessage()
|
urlPreview = findUrlInMessage()
|
||||||
@@ -701,6 +835,10 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
userSuggestions = emptyList()
|
userSuggestions = emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
saveDraft()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun updateToUsers(it: TextFieldValue) {
|
open fun updateToUsers(it: TextFieldValue) {
|
||||||
@@ -724,10 +862,16 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
userSuggestions = emptyList()
|
userSuggestions = emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
saveDraft()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun updateSubject(it: TextFieldValue) {
|
open fun updateSubject(it: TextFieldValue) {
|
||||||
subject = it
|
subject = it
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
saveDraft()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun updateZapForwardTo(it: TextFieldValue) {
|
open fun updateZapForwardTo(it: TextFieldValue) {
|
||||||
@@ -754,6 +898,9 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
userSuggestions = emptyList()
|
userSuggestions = emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
saveDraft()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun autocompleteWithUser(item: User) {
|
open fun autocompleteWithUser(item: User) {
|
||||||
@@ -799,6 +946,10 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
userSuggestionsMainMessage = null
|
userSuggestionsMainMessage = null
|
||||||
userSuggestions = emptyList()
|
userSuggestions = emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
saveDraft()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun newStateMapPollOptions(): SnapshotStateMap<Int, String> {
|
private fun newStateMapPollOptions(): SnapshotStateMap<Int, String> {
|
||||||
@@ -869,6 +1020,9 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
|
|
||||||
message = message.insertUrlAtCursor(imageUrl)
|
message = message.insertUrlAtCursor(imageUrl)
|
||||||
urlPreview = findUrlInMessage()
|
urlPreview = findUrlInMessage()
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
saveDraft()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
@@ -913,6 +1067,9 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
urlPreview = findUrlInMessage()
|
urlPreview = findUrlInMessage()
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
saveDraft()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
@@ -933,6 +1090,7 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
locUtil?.let {
|
locUtil?.let {
|
||||||
location =
|
location =
|
||||||
it.locationStateFlow.mapLatest { it.toGeoHash(GeohashPrecision.KM_5_X_5.digits).toString() }
|
it.locationStateFlow.mapLatest { it.toGeoHash(GeohashPrecision.KM_5_X_5.digits).toString() }
|
||||||
|
viewModelScope.launch(Dispatchers.IO) { saveDraft() }
|
||||||
}
|
}
|
||||||
viewModelScope.launch(Dispatchers.IO) { locUtil?.start() }
|
viewModelScope.launch(Dispatchers.IO) { locUtil?.start() }
|
||||||
}
|
}
|
||||||
@@ -957,6 +1115,11 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
} else {
|
} else {
|
||||||
nip24 = !nip24
|
nip24 = !nip24
|
||||||
}
|
}
|
||||||
|
if (message.text.isNotBlank()) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
saveDraft()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateMinZapAmountForPoll(textMin: String) {
|
fun updateMinZapAmountForPoll(textMin: String) {
|
||||||
@@ -976,6 +1139,9 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkMinMax()
|
checkMinMax()
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
saveDraft()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateMaxZapAmountForPoll(textMax: String) {
|
fun updateMaxZapAmountForPoll(textMax: String) {
|
||||||
@@ -995,6 +1161,9 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkMinMax()
|
checkMinMax()
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
saveDraft()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkMinMax() {
|
fun checkMinMax() {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
|
import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
|
||||||
import com.vitorpamplona.amethyst.commons.hashtags.Lightning
|
import com.vitorpamplona.amethyst.commons.hashtags.Lightning
|
||||||
@@ -46,6 +47,8 @@ import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ZapRaiserRequest(
|
fun ZapRaiserRequest(
|
||||||
@@ -98,6 +101,9 @@ fun ZapRaiserRequest(
|
|||||||
} else {
|
} else {
|
||||||
newPostViewModel.zapRaiserAmount = it.toLongOrNull()
|
newPostViewModel.zapRaiserAmount = it.toLongOrNull()
|
||||||
}
|
}
|
||||||
|
newPostViewModel.viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
newPostViewModel.saveDraft()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
placeholder = {
|
placeholder = {
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ import com.vitorpamplona.amethyst.R
|
|||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
||||||
import com.vitorpamplona.amethyst.service.relays.RelayPoolStatus
|
import com.vitorpamplona.amethyst.service.relays.RelayPoolStatus
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
|
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
|
||||||
import com.vitorpamplona.amethyst.ui.components.ClickableText
|
import com.vitorpamplona.amethyst.ui.components.ClickableText
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
@@ -159,7 +160,10 @@ fun DrawerContent(
|
|||||||
)
|
)
|
||||||
|
|
||||||
ListContent(
|
ListContent(
|
||||||
modifier = Modifier.fillMaxWidth().weight(1f),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
drawerState,
|
drawerState,
|
||||||
openSheet,
|
openSheet,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
@@ -231,7 +235,8 @@ fun ProfileContentTemplate(
|
|||||||
model = profilePicture,
|
model = profilePicture,
|
||||||
contentDescription = stringResource(id = R.string.profile_image),
|
contentDescription = stringResource(id = R.string.profile_image),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.width(100.dp)
|
Modifier
|
||||||
|
.width(100.dp)
|
||||||
.height(100.dp)
|
.height(100.dp)
|
||||||
.clip(shape = CircleShape)
|
.clip(shape = CircleShape)
|
||||||
.border(3.dp, MaterialTheme.colorScheme.background, CircleShape)
|
.border(3.dp, MaterialTheme.colorScheme.background, CircleShape)
|
||||||
@@ -244,7 +249,10 @@ fun ProfileContentTemplate(
|
|||||||
CreateTextWithEmoji(
|
CreateTextWithEmoji(
|
||||||
text = bestDisplayName,
|
text = bestDisplayName,
|
||||||
tags = tags,
|
tags = tags,
|
||||||
modifier = Modifier.padding(top = 7.dp).clickable(onClick = onClick),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(top = 7.dp)
|
||||||
|
.clickable(onClick = onClick),
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
@@ -454,8 +462,17 @@ fun ListContent(
|
|||||||
val proxyPort = remember { mutableStateOf(accountViewModel.account.proxyPort.toString()) }
|
val proxyPort = remember { mutableStateOf(accountViewModel.account.proxyPort.toString()) }
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
var draftText by remember {
|
||||||
|
mutableStateOf<String?>(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
var wantsToPost by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier.fillMaxHeight().verticalScroll(rememberScrollState()),
|
modifier =
|
||||||
|
modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
) {
|
) {
|
||||||
NavigationRow(
|
NavigationRow(
|
||||||
title = stringResource(R.string.profile),
|
title = stringResource(R.string.profile),
|
||||||
@@ -571,6 +588,18 @@ fun ListContent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wantsToPost) {
|
||||||
|
NewPostView(
|
||||||
|
{
|
||||||
|
wantsToPost = false
|
||||||
|
draftText = null
|
||||||
|
coroutineScope.launch { drawerState.close() }
|
||||||
|
},
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (disconnectTorDialog) {
|
if (disconnectTorDialog) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
title = { Text(text = stringResource(R.string.do_you_really_want_to_disable_tor_title)) },
|
title = { Text(text = stringResource(R.string.do_you_really_want_to_disable_tor_title)) },
|
||||||
@@ -662,7 +691,8 @@ fun IconRow(
|
|||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.fillMaxWidth()
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
@@ -693,10 +723,16 @@ fun IconRowRelays(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().clickable { onClick() },
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onClick() },
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(vertical = 15.dp, horizontal = 25.dp),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 15.dp, horizontal = 25.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -737,7 +773,10 @@ fun BottomContent(
|
|||||||
thickness = DividerThickness,
|
thickness = DividerThickness,
|
||||||
)
|
)
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 15.dp),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 15.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
ClickableText(
|
ClickableText(
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ fun NormalChannelCard(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup ->
|
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel, newPostViewModel = null) { showPopup ->
|
||||||
CheckNewAndRenderChannelCard(
|
CheckNewAndRenderChannelCard(
|
||||||
baseNote,
|
baseNote,
|
||||||
routeForLastRead,
|
routeForLastRead,
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ import com.vitorpamplona.amethyst.R
|
|||||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||||
@@ -102,6 +103,7 @@ fun ChatroomMessageCompose(
|
|||||||
innerQuote: Boolean = false,
|
innerQuote: Boolean = false,
|
||||||
parentBackgroundColor: MutableState<Color>? = null,
|
parentBackgroundColor: MutableState<Color>? = null,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
|
newPostViewModel: NewPostViewModel?,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
onWantsToReply: (Note) -> Unit,
|
onWantsToReply: (Note) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -120,6 +122,7 @@ fun ChatroomMessageCompose(
|
|||||||
canPreview,
|
canPreview,
|
||||||
parentBackgroundColor,
|
parentBackgroundColor,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
|
newPostViewModel,
|
||||||
nav,
|
nav,
|
||||||
onWantsToReply,
|
onWantsToReply,
|
||||||
)
|
)
|
||||||
@@ -136,6 +139,7 @@ fun NormalChatNote(
|
|||||||
canPreview: Boolean = true,
|
canPreview: Boolean = true,
|
||||||
parentBackgroundColor: MutableState<Color>? = null,
|
parentBackgroundColor: MutableState<Color>? = null,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
|
newPostViewModel: NewPostViewModel?,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
onWantsToReply: (Note) -> Unit,
|
onWantsToReply: (Note) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -255,6 +259,7 @@ fun NormalChatNote(
|
|||||||
availableBubbleSize,
|
availableBubbleSize,
|
||||||
showDetails,
|
showDetails,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
|
newPostViewModel,
|
||||||
nav,
|
nav,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -265,6 +270,7 @@ fun NormalChatNote(
|
|||||||
popupExpanded = popupExpanded,
|
popupExpanded = popupExpanded,
|
||||||
onDismiss = { popupExpanded = false },
|
onDismiss = { popupExpanded = false },
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
|
newPostViewModel = newPostViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -282,6 +288,7 @@ private fun RenderBubble(
|
|||||||
availableBubbleSize: MutableState<Int>,
|
availableBubbleSize: MutableState<Int>,
|
||||||
showDetails: State<Boolean>,
|
showDetails: State<Boolean>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
|
newPostViewModel: NewPostViewModel?,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val bubbleSize = remember { mutableIntStateOf(0) }
|
val bubbleSize = remember { mutableIntStateOf(0) }
|
||||||
@@ -311,6 +318,7 @@ private fun RenderBubble(
|
|||||||
canPreview,
|
canPreview,
|
||||||
showDetails,
|
showDetails,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
|
newPostViewModel,
|
||||||
nav,
|
nav,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -329,6 +337,7 @@ private fun MessageBubbleLines(
|
|||||||
canPreview: Boolean,
|
canPreview: Boolean,
|
||||||
showDetails: State<Boolean>,
|
showDetails: State<Boolean>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
|
newPostViewModel: NewPostViewModel?,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (drawAuthorInfo) {
|
if (drawAuthorInfo) {
|
||||||
@@ -345,6 +354,7 @@ private fun MessageBubbleLines(
|
|||||||
innerQuote = innerQuote,
|
innerQuote = innerQuote,
|
||||||
backgroundBubbleColor = backgroundBubbleColor,
|
backgroundBubbleColor = backgroundBubbleColor,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
|
newPostViewModel = newPostViewModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
onWantsToReply = onWantsToReply,
|
onWantsToReply = onWantsToReply,
|
||||||
)
|
)
|
||||||
@@ -363,6 +373,9 @@ private fun MessageBubbleLines(
|
|||||||
bubbleSize = bubbleSize,
|
bubbleSize = bubbleSize,
|
||||||
availableBubbleSize = availableBubbleSize,
|
availableBubbleSize = availableBubbleSize,
|
||||||
firstColumn = {
|
firstColumn = {
|
||||||
|
if (baseNote.isDraft()) {
|
||||||
|
DisplayDraftChat()
|
||||||
|
}
|
||||||
IncognitoBadge(baseNote)
|
IncognitoBadge(baseNote)
|
||||||
ChatTimeAgo(baseNote)
|
ChatTimeAgo(baseNote)
|
||||||
RelayBadgesHorizontal(baseNote, accountViewModel, nav = nav)
|
RelayBadgesHorizontal(baseNote, accountViewModel, nav = nav)
|
||||||
@@ -394,11 +407,12 @@ private fun RenderReplyRow(
|
|||||||
innerQuote: Boolean,
|
innerQuote: Boolean,
|
||||||
backgroundBubbleColor: MutableState<Color>,
|
backgroundBubbleColor: MutableState<Color>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
|
newPostViewModel: NewPostViewModel?,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
onWantsToReply: (Note) -> Unit,
|
onWantsToReply: (Note) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (!innerQuote && note.replyTo?.lastOrNull() != null) {
|
if (!innerQuote && note.replyTo?.lastOrNull() != null) {
|
||||||
RenderReply(note, backgroundBubbleColor, accountViewModel, nav, onWantsToReply)
|
RenderReply(note, backgroundBubbleColor, accountViewModel, newPostViewModel, nav, onWantsToReply)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,6 +421,7 @@ private fun RenderReply(
|
|||||||
note: Note,
|
note: Note,
|
||||||
backgroundBubbleColor: MutableState<Color>,
|
backgroundBubbleColor: MutableState<Color>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
|
newPostViewModel: NewPostViewModel?,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
onWantsToReply: (Note) -> Unit,
|
onWantsToReply: (Note) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -425,6 +440,7 @@ private fun RenderReply(
|
|||||||
innerQuote = true,
|
innerQuote = true,
|
||||||
parentBackgroundColor = backgroundBubbleColor,
|
parentBackgroundColor = backgroundBubbleColor,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
|
newPostViewModel = newPostViewModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
onWantsToReply = onWantsToReply,
|
onWantsToReply = onWantsToReply,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
@@ -48,6 +49,7 @@ import androidx.compose.ui.graphics.Brush
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.compositeOver
|
import androidx.compose.ui.graphics.compositeOver
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
import androidx.lifecycle.distinctUntilChanged
|
||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
@@ -105,8 +107,11 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Font12SP
|
||||||
import com.vitorpamplona.amethyst.ui.theme.HalfDoubleVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.HalfDoubleVertSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.HalfEndPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
|
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size30Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size30Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
||||||
@@ -122,6 +127,7 @@ import com.vitorpamplona.amethyst.ui.theme.channelNotePictureModifier
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.grayText
|
import com.vitorpamplona.amethyst.ui.theme.grayText
|
||||||
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
|
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
|
||||||
import com.vitorpamplona.amethyst.ui.theme.normalWithTopMarginNoteModifier
|
import com.vitorpamplona.amethyst.ui.theme.normalWithTopMarginNoteModifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import com.vitorpamplona.amethyst.ui.theme.replyBackground
|
import com.vitorpamplona.amethyst.ui.theme.replyBackground
|
||||||
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||||
import com.vitorpamplona.quartz.events.AppDefinitionEvent
|
import com.vitorpamplona.quartz.events.AppDefinitionEvent
|
||||||
@@ -238,7 +244,7 @@ fun AcceptableNote(
|
|||||||
nav = nav,
|
nav = nav,
|
||||||
)
|
)
|
||||||
else ->
|
else ->
|
||||||
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) {
|
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel, newPostViewModel = null) {
|
||||||
showPopup,
|
showPopup,
|
||||||
->
|
->
|
||||||
CheckNewAndRenderNote(
|
CheckNewAndRenderNote(
|
||||||
@@ -273,7 +279,7 @@ fun AcceptableNote(
|
|||||||
is FileHeaderEvent -> FileHeaderDisplay(baseNote, false, accountViewModel)
|
is FileHeaderEvent -> FileHeaderDisplay(baseNote, false, accountViewModel)
|
||||||
is FileStorageHeaderEvent -> FileStorageHeaderDisplay(baseNote, false, accountViewModel)
|
is FileStorageHeaderEvent -> FileStorageHeaderDisplay(baseNote, false, accountViewModel)
|
||||||
else ->
|
else ->
|
||||||
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) {
|
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel, newPostViewModel = null) {
|
||||||
showPopup,
|
showPopup,
|
||||||
->
|
->
|
||||||
CheckNewAndRenderNote(
|
CheckNewAndRenderNote(
|
||||||
@@ -868,6 +874,29 @@ fun DisplayOtsIfInOriginal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DisplayDraft() {
|
||||||
|
Text(
|
||||||
|
"Draft",
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = HalfStartPadding,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DisplayDraftChat() {
|
||||||
|
Text(
|
||||||
|
"Draft",
|
||||||
|
color = MaterialTheme.colorScheme.placeholderText,
|
||||||
|
modifier = HalfEndPadding,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = Font12SP,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FirstUserInfoRow(
|
fun FirstUserInfoRow(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
@@ -910,6 +939,10 @@ fun FirstUserInfoRow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (baseNote.isDraft()) {
|
||||||
|
DisplayDraft()
|
||||||
|
}
|
||||||
|
|
||||||
TimeAgo(baseNote)
|
TimeAgo(baseNote)
|
||||||
|
|
||||||
MoreOptionsButton(baseNote, editState, accountViewModel, nav)
|
MoreOptionsButton(baseNote, editState, accountViewModel, nav)
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import androidx.compose.material.icons.filled.AlternateEmail
|
|||||||
import androidx.compose.material.icons.filled.Block
|
import androidx.compose.material.icons.filled.Block
|
||||||
import androidx.compose.material.icons.filled.ContentCopy
|
import androidx.compose.material.icons.filled.ContentCopy
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material.icons.filled.Edit
|
||||||
import androidx.compose.material.icons.filled.FormatQuote
|
import androidx.compose.material.icons.filled.FormatQuote
|
||||||
import androidx.compose.material.icons.filled.PersonAdd
|
import androidx.compose.material.icons.filled.PersonAdd
|
||||||
import androidx.compose.material.icons.filled.PersonRemove
|
import androidx.compose.material.icons.filled.PersonRemove
|
||||||
@@ -84,6 +85,8 @@ import androidx.core.graphics.ColorUtils
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.components.SelectTextDialog
|
import com.vitorpamplona.amethyst.ui.components.SelectTextDialog
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
|
||||||
@@ -132,6 +135,7 @@ val externalLinkForNote = { note: Note ->
|
|||||||
fun LongPressToQuickAction(
|
fun LongPressToQuickAction(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
|
newPostViewModel: NewPostViewModel?,
|
||||||
content: @Composable (() -> Unit) -> Unit,
|
content: @Composable (() -> Unit) -> Unit,
|
||||||
) {
|
) {
|
||||||
val popupExpanded = remember { mutableStateOf(false) }
|
val popupExpanded = remember { mutableStateOf(false) }
|
||||||
@@ -140,7 +144,7 @@ fun LongPressToQuickAction(
|
|||||||
|
|
||||||
content(showPopup)
|
content(showPopup)
|
||||||
|
|
||||||
NoteQuickActionMenu(baseNote, popupExpanded.value, hidePopup, accountViewModel)
|
NoteQuickActionMenu(baseNote, popupExpanded.value, hidePopup, accountViewModel, newPostViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -149,20 +153,24 @@ fun NoteQuickActionMenu(
|
|||||||
popupExpanded: Boolean,
|
popupExpanded: Boolean,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
|
newPostViewModel: NewPostViewModel?,
|
||||||
) {
|
) {
|
||||||
val showSelectTextDialog = remember { mutableStateOf(false) }
|
val showSelectTextDialog = remember { mutableStateOf(false) }
|
||||||
val showDeleteAlertDialog = remember { mutableStateOf(false) }
|
val showDeleteAlertDialog = remember { mutableStateOf(false) }
|
||||||
val showBlockAlertDialog = remember { mutableStateOf(false) }
|
val showBlockAlertDialog = remember { mutableStateOf(false) }
|
||||||
val showReportDialog = remember { mutableStateOf(false) }
|
val showReportDialog = remember { mutableStateOf(false) }
|
||||||
|
val editDraftDialog = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
if (popupExpanded) {
|
if (popupExpanded) {
|
||||||
RenderMainPopup(
|
RenderMainPopup(
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
|
newPostViewModel,
|
||||||
note,
|
note,
|
||||||
onDismiss,
|
onDismiss,
|
||||||
showBlockAlertDialog,
|
showBlockAlertDialog,
|
||||||
showDeleteAlertDialog,
|
showDeleteAlertDialog,
|
||||||
showReportDialog,
|
showReportDialog,
|
||||||
|
editDraftDialog,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,16 +207,29 @@ fun NoteQuickActionMenu(
|
|||||||
onDismiss()
|
onDismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (editDraftDialog.value) {
|
||||||
|
NewPostView(
|
||||||
|
onClose = {
|
||||||
|
editDraftDialog.value = false
|
||||||
|
},
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
draft = note,
|
||||||
|
nav = { },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RenderMainPopup(
|
private fun RenderMainPopup(
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
|
newPostViewModel: NewPostViewModel?,
|
||||||
note: Note,
|
note: Note,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
showBlockAlertDialog: MutableState<Boolean>,
|
showBlockAlertDialog: MutableState<Boolean>,
|
||||||
showDeleteAlertDialog: MutableState<Boolean>,
|
showDeleteAlertDialog: MutableState<Boolean>,
|
||||||
showReportDialog: MutableState<Boolean>,
|
showReportDialog: MutableState<Boolean>,
|
||||||
|
editDraftDialog: MutableState<Boolean>,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val primaryLight = lightenColor(MaterialTheme.colorScheme.primary, 0.1f)
|
val primaryLight = lightenColor(MaterialTheme.colorScheme.primary, 0.1f)
|
||||||
@@ -279,6 +300,22 @@ private fun RenderMainPopup(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (note.isDraft()) {
|
||||||
|
VerticalDivider(color = primaryLight)
|
||||||
|
NoteQuickActionItem(
|
||||||
|
Icons.Default.Edit,
|
||||||
|
stringResource(R.string.edit_draft),
|
||||||
|
) {
|
||||||
|
if (newPostViewModel != null) {
|
||||||
|
newPostViewModel.load(accountViewModel, null, null, null, null, note)
|
||||||
|
onDismiss()
|
||||||
|
} else {
|
||||||
|
editDraftDialog.value = true
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isOwnNote) {
|
if (!isOwnNote) {
|
||||||
VerticalDivider(color = primaryLight)
|
VerticalDivider(color = primaryLight)
|
||||||
|
|
||||||
@@ -389,14 +426,20 @@ fun NoteQuickActionItem(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.size(70.dp).clickable { onClick() },
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(70.dp)
|
||||||
|
.clickable { onClick() },
|
||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(24.dp).padding(bottom = 5.dp),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(24.dp)
|
||||||
|
.padding(bottom = 5.dp),
|
||||||
tint = Color.White,
|
tint = Color.White,
|
||||||
)
|
)
|
||||||
Text(text = label, fontSize = 12.sp, color = Color.White, textAlign = TextAlign.Center)
|
Text(text = label, fontSize = 12.sp, color = Color.White, textAlign = TextAlign.Center)
|
||||||
@@ -527,7 +570,10 @@ fun QuickActionAlertDialog(
|
|||||||
text = { Text(textContent) },
|
text = { Text(textContent) },
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(all = 8.dp).fillMaxWidth(),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(all = 8.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
) {
|
) {
|
||||||
TextButton(onClick = onClickDontShowAgain) {
|
TextButton(onClick = onClickDontShowAgain) {
|
||||||
|
|||||||
@@ -310,7 +310,12 @@ fun RenderZapRaiser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
modifier = remember(details) { Modifier.fillMaxWidth().height(if (details) 24.dp else 4.dp) },
|
modifier =
|
||||||
|
remember(details) {
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(if (details) 24.dp else 4.dp)
|
||||||
|
},
|
||||||
color = color,
|
color = color,
|
||||||
progress = { zapraiserStatus.progress },
|
progress = { zapraiserStatus.progress },
|
||||||
)
|
)
|
||||||
@@ -590,6 +595,13 @@ fun ReplyReaction(
|
|||||||
IconButton(
|
IconButton(
|
||||||
modifier = iconSizeModifier,
|
modifier = iconSizeModifier,
|
||||||
onClick = {
|
onClick = {
|
||||||
|
if (baseNote.isDraft()) {
|
||||||
|
accountViewModel.toast(
|
||||||
|
R.string.draft_note,
|
||||||
|
R.string.it_s_not_possible_to_reply_to_a_draft_note,
|
||||||
|
)
|
||||||
|
return@IconButton
|
||||||
|
}
|
||||||
if (accountViewModel.isWriteable()) {
|
if (accountViewModel.isWriteable()) {
|
||||||
onPress()
|
onPress()
|
||||||
} else {
|
} else {
|
||||||
@@ -776,7 +788,8 @@ fun LikeReaction(
|
|||||||
Box(
|
Box(
|
||||||
contentAlignment = Center,
|
contentAlignment = Center,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.size(iconSize)
|
Modifier
|
||||||
|
.size(iconSize)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
role = Role.Button,
|
role = Role.Button,
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
@@ -784,6 +797,7 @@ fun LikeReaction(
|
|||||||
onClick = {
|
onClick = {
|
||||||
likeClick(
|
likeClick(
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
|
baseNote,
|
||||||
onMultipleChoices = { wantsToReact = true },
|
onMultipleChoices = { wantsToReact = true },
|
||||||
onWantsToSignReaction = { accountViewModel.reactToOrDelete(baseNote) },
|
onWantsToSignReaction = { accountViewModel.reactToOrDelete(baseNote) },
|
||||||
)
|
)
|
||||||
@@ -886,9 +900,17 @@ fun ObserveLikeText(
|
|||||||
|
|
||||||
private fun likeClick(
|
private fun likeClick(
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
|
baseNote: Note,
|
||||||
onMultipleChoices: () -> Unit,
|
onMultipleChoices: () -> Unit,
|
||||||
onWantsToSignReaction: () -> Unit,
|
onWantsToSignReaction: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
if (baseNote.isDraft()) {
|
||||||
|
accountViewModel.toast(
|
||||||
|
R.string.draft_note,
|
||||||
|
R.string.it_s_not_possible_to_react_to_a_draft_note,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (accountViewModel.account.reactionChoices.isEmpty()) {
|
if (accountViewModel.account.reactionChoices.isEmpty()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toast(
|
||||||
R.string.no_reactions_setup,
|
R.string.no_reactions_setup,
|
||||||
@@ -1082,6 +1104,14 @@ fun zapClick(
|
|||||||
onError: (String, String) -> Unit,
|
onError: (String, String) -> Unit,
|
||||||
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
onPayViaIntent: (ImmutableList<ZapPaymentHandler.Payable>) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
if (baseNote.isDraft()) {
|
||||||
|
accountViewModel.toast(
|
||||||
|
R.string.draft_note,
|
||||||
|
R.string.it_s_not_possible_to_zap_to_a_draft_note,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (accountViewModel.account.zapAmountChoices.isEmpty()) {
|
if (accountViewModel.account.zapAmountChoices.isEmpty()) {
|
||||||
accountViewModel.toast(
|
accountViewModel.toast(
|
||||||
context.getString(R.string.error_dialog_zap_error),
|
context.getString(R.string.error_dialog_zap_error),
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ fun WatchNoteEvent(
|
|||||||
LongPressToQuickAction(
|
LongPressToQuickAction(
|
||||||
baseNote = baseNote,
|
baseNote = baseNote,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
|
newPostViewModel = null,
|
||||||
) { showPopup ->
|
) { showPopup ->
|
||||||
BlankNote(
|
BlankNote(
|
||||||
remember {
|
remember {
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ fun ZapCustomDialog(
|
|||||||
)
|
)
|
||||||
|
|
||||||
ZapButton(
|
ZapButton(
|
||||||
isActive = postViewModel.canSend(),
|
isActive = postViewModel.canSend() && !baseNote.isDraft(),
|
||||||
) {
|
) {
|
||||||
accountViewModel.zap(
|
accountViewModel.zap(
|
||||||
baseNote,
|
baseNote,
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ class AddBountyAmountViewModel : ViewModel() {
|
|||||||
root = null,
|
root = null,
|
||||||
directMentions = setOf(),
|
directMentions = setOf(),
|
||||||
forkedFrom = null,
|
forkedFrom = null,
|
||||||
|
draftTag = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
nextAmount = TextFieldValue("")
|
nextAmount = TextFieldValue("")
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import androidx.core.content.ContextCompat
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.actions.EditPostView
|
import com.vitorpamplona.amethyst.ui.actions.EditPostView
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||||
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
||||||
import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon
|
import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.externalLinkForNote
|
import com.vitorpamplona.amethyst.ui.note.externalLinkForNote
|
||||||
@@ -122,6 +123,11 @@ fun NoteDropDownMenu(
|
|||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val wantsToEditDraft =
|
||||||
|
remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
if (wantsToEditPost.value) {
|
if (wantsToEditPost.value) {
|
||||||
// avoids changing while drafting a note and a new event shows up.
|
// avoids changing while drafting a note and a new event shows up.
|
||||||
val versionLookingAt =
|
val versionLookingAt =
|
||||||
@@ -141,6 +147,18 @@ fun NoteDropDownMenu(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wantsToEditDraft.value) {
|
||||||
|
NewPostView(
|
||||||
|
onClose = {
|
||||||
|
popupExpanded.value = false
|
||||||
|
wantsToEditDraft.value = false
|
||||||
|
},
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
draft = note,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = popupExpanded.value,
|
expanded = popupExpanded.value,
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
@@ -219,7 +237,15 @@ fun NoteDropDownMenu(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
HorizontalDivider(thickness = DividerThickness)
|
HorizontalDivider(thickness = DividerThickness)
|
||||||
if (note.event is TextNoteEvent) {
|
if (note.isDraft()) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.edit_draft)) },
|
||||||
|
onClick = {
|
||||||
|
wantsToEditDraft.value = true
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (note.event is TextNoteEvent && !note.isDraft()) {
|
||||||
if (state.isLoggedUser) {
|
if (state.isLoggedUser) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text(stringResource(R.string.edit_post)) },
|
text = { Text(stringResource(R.string.edit_post)) },
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose
|
import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||||
@@ -48,6 +49,7 @@ import com.vitorpamplona.amethyst.ui.theme.HalfPadding
|
|||||||
fun RefreshingChatroomFeedView(
|
fun RefreshingChatroomFeedView(
|
||||||
viewModel: FeedViewModel,
|
viewModel: FeedViewModel,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
|
newPostViewModel: NewPostViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
routeForLastRead: String,
|
routeForLastRead: String,
|
||||||
onWantsToReply: (Note) -> Unit,
|
onWantsToReply: (Note) -> Unit,
|
||||||
@@ -59,6 +61,7 @@ fun RefreshingChatroomFeedView(
|
|||||||
RenderChatroomFeedView(
|
RenderChatroomFeedView(
|
||||||
viewModel,
|
viewModel,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
|
newPostViewModel,
|
||||||
listState,
|
listState,
|
||||||
nav,
|
nav,
|
||||||
routeForLastRead,
|
routeForLastRead,
|
||||||
@@ -72,6 +75,7 @@ fun RefreshingChatroomFeedView(
|
|||||||
fun RenderChatroomFeedView(
|
fun RenderChatroomFeedView(
|
||||||
viewModel: FeedViewModel,
|
viewModel: FeedViewModel,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
|
newPostViewModel: NewPostViewModel,
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
routeForLastRead: String,
|
routeForLastRead: String,
|
||||||
@@ -91,6 +95,7 @@ fun RenderChatroomFeedView(
|
|||||||
ChatroomFeedLoaded(
|
ChatroomFeedLoaded(
|
||||||
state,
|
state,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
|
newPostViewModel,
|
||||||
listState,
|
listState,
|
||||||
nav,
|
nav,
|
||||||
routeForLastRead,
|
routeForLastRead,
|
||||||
@@ -108,6 +113,7 @@ fun RenderChatroomFeedView(
|
|||||||
fun ChatroomFeedLoaded(
|
fun ChatroomFeedLoaded(
|
||||||
state: FeedState.Loaded,
|
state: FeedState.Loaded,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
|
newPostViewModel: NewPostViewModel,
|
||||||
listState: LazyListState,
|
listState: LazyListState,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
routeForLastRead: String,
|
routeForLastRead: String,
|
||||||
@@ -130,6 +136,7 @@ fun ChatroomFeedLoaded(
|
|||||||
baseNote = item,
|
baseNote = item,
|
||||||
routeForLastRead = routeForLastRead,
|
routeForLastRead = routeForLastRead,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
|
newPostViewModel = newPostViewModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
onWantsToReply = onWantsToReply,
|
onWantsToReply = onWantsToReply,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
|
|||||||
import com.vitorpamplona.amethyst.ui.components.mockAccountViewModel
|
import com.vitorpamplona.amethyst.ui.components.mockAccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
|
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
|
||||||
import com.vitorpamplona.amethyst.ui.note.BlankNote
|
import com.vitorpamplona.amethyst.ui.note.BlankNote
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.DisplayDraft
|
||||||
import com.vitorpamplona.amethyst.ui.note.DisplayOtsIfInOriginal
|
import com.vitorpamplona.amethyst.ui.note.DisplayOtsIfInOriginal
|
||||||
import com.vitorpamplona.amethyst.ui.note.HiddenNote
|
import com.vitorpamplona.amethyst.ui.note.HiddenNote
|
||||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||||
@@ -446,6 +447,10 @@ fun NoteMaster(
|
|||||||
DisplayPoW(pow)
|
DisplayPoW(pow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (note.isDraft()) {
|
||||||
|
DisplayDraft()
|
||||||
|
}
|
||||||
|
|
||||||
DisplayOtsIfInOriginal(note, editState, accountViewModel)
|
DisplayOtsIfInOriginal(note, editState, accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -605,7 +610,7 @@ fun NoteMaster(
|
|||||||
ReactionsRow(note, true, editState, accountViewModel, nav)
|
ReactionsRow(note, true, editState, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
NoteQuickActionMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
|
NoteQuickActionMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1209,6 +1209,14 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
|
|||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
onMore: () -> Unit,
|
onMore: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
if (baseNote.isDraft()) {
|
||||||
|
toast(
|
||||||
|
R.string.draft_note,
|
||||||
|
R.string.it_s_not_possible_to_quote_to_a_draft_note,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (isWriteable()) {
|
if (isWriteable()) {
|
||||||
if (hasBoosted(baseNote)) {
|
if (hasBoosted(baseNote)) {
|
||||||
deleteBoostsTo(baseNote)
|
deleteBoostsTo(baseNote)
|
||||||
@@ -1304,6 +1312,11 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun deleteDraft(draftTag: String) {
|
||||||
|
val notes = LocalCache.draftNotes(draftTag)
|
||||||
|
account.delete(notes)
|
||||||
|
}
|
||||||
|
|
||||||
val bechLinkCache = CachedLoadedBechLink(this)
|
val bechLinkCache = CachedLoadedBechLink(this)
|
||||||
|
|
||||||
class CachedLoadedBechLink(val accountViewModel: AccountViewModel) : GenericBaseCache<String, LoadedBechLink>(20) {
|
class CachedLoadedBechLink(val accountViewModel: AccountViewModel) : GenericBaseCache<String, LoadedBechLink>(20) {
|
||||||
|
|||||||
@@ -163,12 +163,17 @@ import kotlinx.collections.immutable.ImmutableList
|
|||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChannelScreen(
|
fun ChannelScreen(
|
||||||
@@ -187,6 +192,7 @@ fun ChannelScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun PrepareChannelViewModels(
|
fun PrepareChannelViewModels(
|
||||||
baseChannel: Channel,
|
baseChannel: Channel,
|
||||||
@@ -204,8 +210,21 @@ fun PrepareChannelViewModels(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val channelScreenModel: NewPostViewModel = viewModel()
|
val channelScreenModel: NewPostViewModel = viewModel()
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
channelScreenModel.draftTextChanges
|
||||||
|
.receiveAsFlow()
|
||||||
|
.debounce(1000)
|
||||||
|
.collectLatest {
|
||||||
|
channelScreenModel.sendPost(localDraft = channelScreenModel.draftTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
channelScreenModel.accountViewModel = accountViewModel
|
channelScreenModel.accountViewModel = accountViewModel
|
||||||
channelScreenModel.account = accountViewModel.account
|
channelScreenModel.account = accountViewModel.account
|
||||||
|
channelScreenModel.originalNote = LocalCache.getNoteIfExists(baseChannel.idHex)
|
||||||
|
|
||||||
ChannelScreen(
|
ChannelScreen(
|
||||||
channel = baseChannel,
|
channel = baseChannel,
|
||||||
@@ -287,6 +306,7 @@ fun ChannelScreen(
|
|||||||
RefreshingChatroomFeedView(
|
RefreshingChatroomFeedView(
|
||||||
viewModel = feedViewModel,
|
viewModel = feedViewModel,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
|
newPostViewModel = newPostModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
routeForLastRead = "Channel/${channel.idHex}",
|
routeForLastRead = "Channel/${channel.idHex}",
|
||||||
onWantsToReply = { replyTo.value = it },
|
onWantsToReply = { replyTo.value = it },
|
||||||
@@ -295,7 +315,7 @@ fun ChannelScreen(
|
|||||||
|
|
||||||
Spacer(modifier = DoubleVertSpacer)
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
|
|
||||||
replyTo.value?.let { DisplayReplyingToNote(it, accountViewModel, nav) { replyTo.value = null } }
|
replyTo.value?.let { DisplayReplyingToNote(it, accountViewModel, newPostModel, nav) { replyTo.value = null } }
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
@@ -323,6 +343,7 @@ fun ChannelScreen(
|
|||||||
mentions = tagger.pTags,
|
mentions = tagger.pTags,
|
||||||
wantsToMarkAsSensitive = false,
|
wantsToMarkAsSensitive = false,
|
||||||
nip94attachments = usedAttachments,
|
nip94attachments = usedAttachments,
|
||||||
|
draftTag = null,
|
||||||
)
|
)
|
||||||
} else if (channel is LiveActivitiesChannel) {
|
} else if (channel is LiveActivitiesChannel) {
|
||||||
accountViewModel.account.sendLiveMessage(
|
accountViewModel.account.sendLiveMessage(
|
||||||
@@ -332,10 +353,13 @@ fun ChannelScreen(
|
|||||||
mentions = tagger.pTags,
|
mentions = tagger.pTags,
|
||||||
wantsToMarkAsSensitive = false,
|
wantsToMarkAsSensitive = false,
|
||||||
nip94attachments = usedAttachments,
|
nip94attachments = usedAttachments,
|
||||||
|
draftTag = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
newPostModel.message = TextFieldValue("")
|
newPostModel.message = TextFieldValue("")
|
||||||
replyTo.value = null
|
replyTo.value = null
|
||||||
|
accountViewModel.deleteDraft(newPostModel.draftTag)
|
||||||
|
newPostModel.draftTag = UUID.randomUUID().toString()
|
||||||
feedViewModel.sendToTop()
|
feedViewModel.sendToTop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,6 +370,7 @@ fun ChannelScreen(
|
|||||||
fun DisplayReplyingToNote(
|
fun DisplayReplyingToNote(
|
||||||
replyingNote: Note?,
|
replyingNote: Note?,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
|
newPostModel: NewPostViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
onCancel: () -> Unit,
|
onCancel: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -364,6 +389,7 @@ fun DisplayReplyingToNote(
|
|||||||
null,
|
null,
|
||||||
innerQuote = true,
|
innerQuote = true,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
|
newPostViewModel = newPostModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
onWantsToReply = {},
|
onWantsToReply = {},
|
||||||
)
|
)
|
||||||
@@ -665,7 +691,12 @@ fun ShowVideoStreaming(
|
|||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
modifier = remember { Modifier.fillMaxWidth().heightIn(min = 50.dp, max = 300.dp) },
|
modifier =
|
||||||
|
remember {
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = 50.dp, max = 300.dp)
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
val zoomableUrlVideo =
|
val zoomableUrlVideo =
|
||||||
remember(streamingInfo) {
|
remember(streamingInfo) {
|
||||||
|
|||||||
@@ -116,14 +116,21 @@ import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
|
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
|
import com.vitorpamplona.quartz.encoders.Hex
|
||||||
|
import com.vitorpamplona.quartz.encoders.toNpub
|
||||||
import com.vitorpamplona.quartz.events.ChatMessageEvent
|
import com.vitorpamplona.quartz.events.ChatMessageEvent
|
||||||
import com.vitorpamplona.quartz.events.ChatroomKey
|
import com.vitorpamplona.quartz.events.ChatroomKey
|
||||||
import com.vitorpamplona.quartz.events.findURLs
|
import com.vitorpamplona.quartz.events.findURLs
|
||||||
import kotlinx.collections.immutable.persistentSetOf
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatroomScreen(
|
fun ChatroomScreen(
|
||||||
@@ -206,6 +213,7 @@ fun LoadRoomByAuthor(
|
|||||||
content(room)
|
content(room)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun PrepareChatroomViewModels(
|
fun PrepareChatroomViewModels(
|
||||||
room: ChatroomKey,
|
room: ChatroomKey,
|
||||||
@@ -230,8 +238,20 @@ fun PrepareChatroomViewModels(
|
|||||||
if (newPostModel.requiresNIP24) {
|
if (newPostModel.requiresNIP24) {
|
||||||
newPostModel.nip24 = true
|
newPostModel.nip24 = true
|
||||||
}
|
}
|
||||||
|
room.users.forEach {
|
||||||
|
newPostModel.toUsers = TextFieldValue(newPostModel.toUsers.text + " @${Hex.decode(it).toNpub()}")
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = newPostModel) {
|
LaunchedEffect(key1 = newPostModel) {
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
newPostModel.draftTextChanges
|
||||||
|
.receiveAsFlow()
|
||||||
|
.debounce(1000)
|
||||||
|
.collectLatest {
|
||||||
|
newPostModel.sendPost(localDraft = newPostModel.draftTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
val hasNIP24 =
|
val hasNIP24 =
|
||||||
accountViewModel.userProfile().privateChatrooms[room]?.roomMessages?.any {
|
accountViewModel.userProfile().privateChatrooms[room]?.roomMessages?.any {
|
||||||
@@ -313,21 +333,28 @@ fun ChatroomScreen(
|
|||||||
RefreshingChatroomFeedView(
|
RefreshingChatroomFeedView(
|
||||||
viewModel = feedViewModel,
|
viewModel = feedViewModel,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
|
newPostViewModel = newPostModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
routeForLastRead = "Room/${room.hashCode()}",
|
routeForLastRead = "Room/${room.hashCode()}",
|
||||||
onWantsToReply = { replyTo.value = it },
|
onWantsToReply = {
|
||||||
|
replyTo.value = it
|
||||||
|
newPostModel.originalNote = it
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(10.dp))
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
|
||||||
replyTo.value?.let { DisplayReplyingToNote(it, accountViewModel, nav) { replyTo.value = null } }
|
replyTo.value?.let { DisplayReplyingToNote(it, accountViewModel, newPostModel, nav) { replyTo.value = null } }
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
// LAST ROW
|
// LAST ROW
|
||||||
PrivateMessageEditFieldRow(newPostModel, isPrivate = true, accountViewModel) {
|
PrivateMessageEditFieldRow(newPostModel, isPrivate = true, accountViewModel) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
|
accountViewModel.deleteDraft(newPostModel.draftTag)
|
||||||
|
newPostModel.draftTag = UUID.randomUUID().toString()
|
||||||
|
|
||||||
val urls = findURLs(newPostModel.message.text)
|
val urls = findURLs(newPostModel.message.text)
|
||||||
val usedAttachments = newPostModel.nip94attachments.filter { it.urls().intersect(urls.toSet()).isNotEmpty() }
|
val usedAttachments = newPostModel.nip94attachments.filter { it.urls().intersect(urls.toSet()).isNotEmpty() }
|
||||||
|
|
||||||
@@ -339,6 +366,7 @@ fun ChatroomScreen(
|
|||||||
mentions = null,
|
mentions = null,
|
||||||
wantsToMarkAsSensitive = false,
|
wantsToMarkAsSensitive = false,
|
||||||
nip94attachments = usedAttachments,
|
nip94attachments = usedAttachments,
|
||||||
|
draftTag = null,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
accountViewModel.account.sendPrivateMessage(
|
accountViewModel.account.sendPrivateMessage(
|
||||||
@@ -348,6 +376,7 @@ fun ChatroomScreen(
|
|||||||
mentions = null,
|
mentions = null,
|
||||||
wantsToMarkAsSensitive = false,
|
wantsToMarkAsSensitive = false,
|
||||||
nip94attachments = usedAttachments,
|
nip94attachments = usedAttachments,
|
||||||
|
draftTag = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ val Size40dp = 40.dp
|
|||||||
val Size55dp = 55.dp
|
val Size55dp = 55.dp
|
||||||
val Size75dp = 75.dp
|
val Size75dp = 75.dp
|
||||||
|
|
||||||
|
val HalfEndPadding = Modifier.padding(end = 5.dp)
|
||||||
val HalfStartPadding = Modifier.padding(start = 5.dp)
|
val HalfStartPadding = Modifier.padding(start = 5.dp)
|
||||||
val StdStartPadding = Modifier.padding(start = 10.dp)
|
val StdStartPadding = Modifier.padding(start = 10.dp)
|
||||||
val StdTopPadding = Modifier.padding(top = 10.dp)
|
val StdTopPadding = Modifier.padding(top = 10.dp)
|
||||||
|
|||||||
@@ -733,6 +733,8 @@
|
|||||||
<string name="could_not_download_from_the_server">Could not download uploaded media from the server</string>
|
<string name="could_not_download_from_the_server">Could not download uploaded media from the server</string>
|
||||||
<string name="could_not_prepare_local_file_to_upload">Could not prepare local file to upload: %1$s</string>
|
<string name="could_not_prepare_local_file_to_upload">Could not prepare local file to upload: %1$s</string>
|
||||||
|
|
||||||
|
<string name="edit_draft">Edit draft</string>
|
||||||
|
|
||||||
<string name="login_with_qr_code">Login with QR Code</string>
|
<string name="login_with_qr_code">Login with QR Code</string>
|
||||||
<string name="route">Route</string>
|
<string name="route">Route</string>
|
||||||
<string name="route_home">Home</string>
|
<string name="route_home">Home</string>
|
||||||
@@ -819,4 +821,9 @@
|
|||||||
<string name="accessibility_play_username">Play username as audio</string>
|
<string name="accessibility_play_username">Play username as audio</string>
|
||||||
<string name="accessibility_scan_qr_code">Scan QR code</string>
|
<string name="accessibility_scan_qr_code">Scan QR code</string>
|
||||||
<string name="accessibility_navigate_to_alby">Navigate to the third-party wallet provider Alby</string>
|
<string name="accessibility_navigate_to_alby">Navigate to the third-party wallet provider Alby</string>
|
||||||
|
<string name="it_s_not_possible_to_reply_to_a_draft_note">It\'s not possible to reply a draft note</string>
|
||||||
|
<string name="it_s_not_possible_to_quote_to_a_draft_note">It\'s not possible to quote a draft note</string>
|
||||||
|
<string name="it_s_not_possible_to_react_to_a_draft_note">It\'s not possible to react a draft note</string>
|
||||||
|
<string name="it_s_not_possible_to_zap_to_a_draft_note">It\'s not possible to zap a draft note</string>
|
||||||
|
<string name="draft_note">Draft Note</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class ChannelMessageEvent(
|
|||||||
zapRaiserAmount: Long?,
|
zapRaiserAmount: Long?,
|
||||||
geohash: String? = null,
|
geohash: String? = null,
|
||||||
nip94attachments: List<FileHeaderEvent>? = null,
|
nip94attachments: List<FileHeaderEvent>? = null,
|
||||||
|
isDraft: Boolean,
|
||||||
onReady: (ChannelMessageEvent) -> Unit,
|
onReady: (ChannelMessageEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
val tags =
|
val tags =
|
||||||
@@ -87,7 +88,7 @@ class ChannelMessageEvent(
|
|||||||
arrayOf("alt", ALT),
|
arrayOf("alt", ALT),
|
||||||
)
|
)
|
||||||
|
|
||||||
signer.sign(createdAt, KIND, tags.toTypedArray(), message, onReady)
|
signer.sign(createdAt, KIND, tags.toTypedArray(), message, onReady, isDraft)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ class ChatMessageEvent(
|
|||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
nip94attachments: List<FileHeaderEvent>? = null,
|
nip94attachments: List<FileHeaderEvent>? = null,
|
||||||
|
isDraft: Boolean,
|
||||||
onReady: (ChatMessageEvent) -> Unit,
|
onReady: (ChatMessageEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
val tags = mutableListOf<Array<String>>()
|
val tags = mutableListOf<Array<String>>()
|
||||||
@@ -106,7 +107,7 @@ class ChatMessageEvent(
|
|||||||
}
|
}
|
||||||
// tags.add(arrayOf("alt", alt))
|
// tags.add(arrayOf("alt", alt))
|
||||||
|
|
||||||
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady)
|
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady, isDraft)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ class ClassifiedsEvent(
|
|||||||
nip94attachments: List<Event>? = null,
|
nip94attachments: List<Event>? = null,
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
isDraft: Boolean,
|
||||||
onReady: (ClassifiedsEvent) -> Unit,
|
onReady: (ClassifiedsEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
val tags = mutableListOf<Array<String>>()
|
val tags = mutableListOf<Array<String>>()
|
||||||
@@ -192,7 +193,7 @@ class ClassifiedsEvent(
|
|||||||
}
|
}
|
||||||
tags.add(arrayOf("alt", ALT))
|
tags.add(arrayOf("alt", ALT))
|
||||||
|
|
||||||
signer.sign(createdAt, KIND, tags.toTypedArray(), message, onReady)
|
signer.sign(createdAt, KIND, tags.toTypedArray(), message, onReady, isDraft)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
/**
|
||||||
|
* 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.encoders.Nip19Bech32
|
||||||
|
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class DraftEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: Array<Array<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey,
|
||||||
|
) : 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 findCitations(): Set<HexKey> {
|
||||||
|
citedNotesCache?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -79,6 +79,7 @@ class EventFactory {
|
|||||||
CommunityPostApprovalEvent(id, pubKey, createdAt, tags, content, sig)
|
CommunityPostApprovalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
ContactListEvent.KIND -> ContactListEvent(id, pubKey, createdAt, tags, content, sig)
|
ContactListEvent.KIND -> ContactListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
DeletionEvent.KIND -> DeletionEvent(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)
|
EmojiPackEvent.KIND -> EmojiPackEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
EmojiPackSelectionEvent.KIND ->
|
EmojiPackSelectionEvent.KIND ->
|
||||||
EmojiPackSelectionEvent(id, pubKey, createdAt, tags, content, sig)
|
EmojiPackSelectionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ class GitReplyEvent(
|
|||||||
forkedFrom: Event? = null,
|
forkedFrom: Event? = null,
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
isDraft: Boolean,
|
||||||
onReady: (GitReplyEvent) -> Unit,
|
onReady: (GitReplyEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
val tags = mutableListOf<Array<String>>()
|
val tags = mutableListOf<Array<String>>()
|
||||||
@@ -156,7 +157,7 @@ class GitReplyEvent(
|
|||||||
}
|
}
|
||||||
tags.add(arrayOf("alt", "a git issue reply"))
|
tags.add(arrayOf("alt", "a git issue reply"))
|
||||||
|
|
||||||
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady)
|
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady, isDraft)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ class LiveActivitiesChatMessageEvent(
|
|||||||
zapRaiserAmount: Long?,
|
zapRaiserAmount: Long?,
|
||||||
geohash: String? = null,
|
geohash: String? = null,
|
||||||
nip94attachments: List<FileHeaderEvent>? = null,
|
nip94attachments: List<FileHeaderEvent>? = null,
|
||||||
|
isDraft: Boolean,
|
||||||
onReady: (LiveActivitiesChatMessageEvent) -> Unit,
|
onReady: (LiveActivitiesChatMessageEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
val content = message
|
val content = message
|
||||||
@@ -98,7 +99,7 @@ class LiveActivitiesChatMessageEvent(
|
|||||||
}
|
}
|
||||||
tags.add(arrayOf("alt", ALT))
|
tags.add(arrayOf("alt", ALT))
|
||||||
|
|
||||||
signer.sign(createdAt, KIND, tags.toTypedArray(), content, onReady)
|
signer.sign(createdAt, KIND, tags.toTypedArray(), content, onReady, isDraft)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.quartz.events
|
package com.vitorpamplona.quartz.events
|
||||||
|
|
||||||
import com.vitorpamplona.quartz.encoders.ATag
|
|
||||||
import com.vitorpamplona.quartz.encoders.HexKey
|
import com.vitorpamplona.quartz.encoders.HexKey
|
||||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||||
|
|
||||||
@@ -78,6 +77,7 @@ class NIP24Factory {
|
|||||||
zapRaiserAmount: Long? = null,
|
zapRaiserAmount: Long? = null,
|
||||||
geohash: String? = null,
|
geohash: String? = null,
|
||||||
nip94attachments: List<FileHeaderEvent>? = null,
|
nip94attachments: List<FileHeaderEvent>? = null,
|
||||||
|
draftTag: String? = null,
|
||||||
onReady: (Result) -> Unit,
|
onReady: (Result) -> Unit,
|
||||||
) {
|
) {
|
||||||
val senderPublicKey = signer.pubKey
|
val senderPublicKey = signer.pubKey
|
||||||
@@ -93,15 +93,25 @@ class NIP24Factory {
|
|||||||
markAsSensitive = markAsSensitive,
|
markAsSensitive = markAsSensitive,
|
||||||
zapRaiserAmount = zapRaiserAmount,
|
zapRaiserAmount = zapRaiserAmount,
|
||||||
geohash = geohash,
|
geohash = geohash,
|
||||||
|
isDraft = draftTag != null,
|
||||||
nip94attachments = nip94attachments,
|
nip94attachments = nip94attachments,
|
||||||
) { senderMessage ->
|
) { senderMessage ->
|
||||||
createWraps(senderMessage, to.plus(senderPublicKey).toSet(), signer) { wraps ->
|
if (draftTag != null) {
|
||||||
onReady(
|
onReady(
|
||||||
Result(
|
Result(
|
||||||
msg = senderMessage,
|
msg = senderMessage,
|
||||||
wraps = wraps,
|
wraps = listOf(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
createWraps(senderMessage, to.plus(senderPublicKey).toSet(), signer) { wraps ->
|
||||||
|
onReady(
|
||||||
|
Result(
|
||||||
|
msg = senderMessage,
|
||||||
|
wraps = wraps,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,49 +165,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,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ class PollNoteEvent(
|
|||||||
zapRaiserAmount: Long?,
|
zapRaiserAmount: Long?,
|
||||||
geohash: String? = null,
|
geohash: String? = null,
|
||||||
nip94attachments: List<FileHeaderEvent>? = null,
|
nip94attachments: List<FileHeaderEvent>? = null,
|
||||||
|
isDraft: Boolean,
|
||||||
onReady: (PollNoteEvent) -> Unit,
|
onReady: (PollNoteEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
val tags = mutableListOf<Array<String>>()
|
val tags = mutableListOf<Array<String>>()
|
||||||
@@ -112,7 +113,7 @@ class PollNoteEvent(
|
|||||||
}
|
}
|
||||||
tags.add(arrayOf("alt", ALT))
|
tags.add(arrayOf("alt", ALT))
|
||||||
|
|
||||||
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady)
|
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady, isDraft)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ class PrivateDmEvent(
|
|||||||
zapRaiserAmount: Long?,
|
zapRaiserAmount: Long?,
|
||||||
geohash: String? = null,
|
geohash: String? = null,
|
||||||
nip94attachments: List<FileHeaderEvent>? = null,
|
nip94attachments: List<FileHeaderEvent>? = null,
|
||||||
|
isDraft: Boolean,
|
||||||
onReady: (PrivateDmEvent) -> Unit,
|
onReady: (PrivateDmEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
var message = msg
|
var message = msg
|
||||||
@@ -167,7 +168,7 @@ class PrivateDmEvent(
|
|||||||
tags.add(arrayOf("alt", ALT))
|
tags.add(arrayOf("alt", ALT))
|
||||||
|
|
||||||
signer.nip04Encrypt(message, recipientPubKey) { content ->
|
signer.nip04Encrypt(message, recipientPubKey) { content ->
|
||||||
signer.sign(createdAt, KIND, tags.toTypedArray(), content, onReady)
|
signer.sign(createdAt, KIND, tags.toTypedArray(), content, onReady, isDraft)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class TextNoteEvent(
|
|||||||
forkedFrom: Event? = null,
|
forkedFrom: Event? = null,
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
isDraft: Boolean,
|
||||||
onReady: (TextNoteEvent) -> Unit,
|
onReady: (TextNoteEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
val tags = mutableListOf<Array<String>>()
|
val tags = mutableListOf<Array<String>>()
|
||||||
@@ -124,7 +125,7 @@ class TextNoteEvent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady)
|
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady, isDraft)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,6 +171,10 @@ class ExternalSignerLauncher(
|
|||||||
"sign_event",
|
"sign_event",
|
||||||
22242,
|
22242,
|
||||||
),
|
),
|
||||||
|
Permission(
|
||||||
|
"sign_event",
|
||||||
|
31234,
|
||||||
|
),
|
||||||
Permission(
|
Permission(
|
||||||
"nip04_encrypt",
|
"nip04_encrypt",
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ abstract class NostrSigner(val pubKey: HexKey) {
|
|||||||
tags: Array<Array<String>>,
|
tags: Array<Array<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
onReady: (T) -> Unit,
|
onReady: (T) -> Unit,
|
||||||
|
isDraft: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
abstract fun nip04Encrypt(
|
abstract fun nip04Encrypt(
|
||||||
|
|||||||
@@ -40,7 +40,13 @@ class NostrSignerExternal(
|
|||||||
tags: Array<Array<String>>,
|
tags: Array<Array<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
onReady: (T) -> Unit,
|
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 id = Event.generateId(pubKey, createdAt, kind, tags, content).toHexKey()
|
||||||
|
|
||||||
val event =
|
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(
|
override fun nip04Encrypt(
|
||||||
decryptedContent: String,
|
decryptedContent: String,
|
||||||
toPublicKey: HexKey,
|
toPublicKey: HexKey,
|
||||||
|
|||||||
@@ -38,9 +38,15 @@ class NostrSignerInternal(val keyPair: KeyPair) : NostrSigner(keyPair.pubKey.toH
|
|||||||
tags: Array<Array<String>>,
|
tags: Array<Array<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
onReady: (T) -> Unit,
|
onReady: (T) -> Unit,
|
||||||
|
isDraft: Boolean,
|
||||||
) {
|
) {
|
||||||
if (keyPair.privKey == null) return
|
if (keyPair.privKey == null) return
|
||||||
|
|
||||||
|
if (isDraft) {
|
||||||
|
unsignedEvent(createdAt, kind, tags, content, onReady)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (isUnsignedPrivateEvent(kind, tags)) {
|
if (isUnsignedPrivateEvent(kind, tags)) {
|
||||||
// this is a private zap
|
// this is a private zap
|
||||||
signPrivateZap(createdAt, kind, tags, content, onReady)
|
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(
|
override fun nip04Encrypt(
|
||||||
decryptedContent: String,
|
decryptedContent: String,
|
||||||
toPublicKey: HexKey,
|
toPublicKey: HexKey,
|
||||||
|
|||||||
Reference in New Issue
Block a user