Cleaning up the saving draft concurrency issues.

This commit is contained in:
Vitor Pamplona
2025-08-05 11:48:02 -04:00
parent a747f25906
commit f1b15cc3c5
7 changed files with 74 additions and 94 deletions

View File

@@ -44,6 +44,7 @@ class DraftTagState {
fun rotate() {
set(newTag())
_versions.update { 0 }
}
fun set(existingTag: String) {

View File

@@ -125,8 +125,8 @@ open class CommentPostViewModel :
}
}
var accountViewModel: AccountViewModel? = null
var account: Account? = null
lateinit var accountViewModel: AccountViewModel
lateinit var account: Account
var externalIdentity by mutableStateOf<ExternalId?>(null)
var replyingTo: Note? by mutableStateOf(null)
@@ -175,11 +175,11 @@ open class CommentPostViewModel :
var wantsZapraiser by mutableStateOf(false)
override val zapRaiserAmount = mutableStateOf<Long?>(null)
fun lnAddress(): String? = account?.userProfile()?.info?.lnAddress()
fun lnAddress(): String? = account.userProfile().info?.lnAddress()
fun hasLnAddress(): Boolean = account?.userProfile()?.info?.lnAddress() != null
fun hasLnAddress(): Boolean = account.userProfile().info?.lnAddress() != null
fun user(): User? = account?.userProfile()
fun user(): User? = account.userProfile()
open fun init(accountVM: AccountViewModel) {
this.accountViewModel = accountVM
@@ -199,8 +199,6 @@ open class CommentPostViewModel :
}
fun editFromDraft(draft: Note) {
val accountViewModel = accountViewModel ?: return
val noteEvent = draft.event
val noteAuthor = draft.author
@@ -223,8 +221,6 @@ open class CommentPostViewModel :
}
open fun quote(quote: Note) {
val accountViewModel = accountViewModel ?: return
message = TextFieldValue(message.text + "\nnostr:${quote.toNEvent()}")
quote.author?.let { quotedUser ->
@@ -259,8 +255,8 @@ open class CommentPostViewModel :
val scope = draftEvent.scope() ?: return
this.externalIdentity = scope
canAddInvoice = accountViewModel?.userProfile()?.info?.lnAddress() != null
canAddZapRaiser = accountViewModel?.userProfile()?.info?.lnAddress() != null
canAddInvoice = accountViewModel.userProfile().info?.lnAddress() != null
canAddZapRaiser = accountViewModel.userProfile().info?.lnAddress() != null
multiOrchestrator = null
val localForwardZapTo = draftEvent.tags.filter { it.size > 1 && it[0] == "zap" }
@@ -316,16 +312,15 @@ open class CommentPostViewModel :
}
}
accountViewModel?.account?.signAndComputeBroadcast(template, extraNotesToBroadcast)
accountViewModel?.deleteDraft(draftTag.current)
val version = draftTag.current
cancel()
accountViewModel.account.signAndComputeBroadcast(template, extraNotesToBroadcast)
accountViewModel.deleteDraft(version)
}
suspend fun sendDraftSync() {
val accountViewModel = accountViewModel ?: return
if (message.text.isBlank()) {
accountViewModel.account.deleteDraft(draftTag.current)
} else {
@@ -333,15 +328,13 @@ open class CommentPostViewModel :
accountViewModel.account.createAndSendDraft(draftTag.current, template)
nip95attachments.forEach {
account?.sendToPrivateOutboxAndLocal(it.first)
account?.sendToPrivateOutboxAndLocal(it.second)
account.sendToPrivateOutboxAndLocal(it.first)
account.sendToPrivateOutboxAndLocal(it.second)
}
}
}
private suspend fun createTemplate(): EventTemplate<out Event>? {
val accountViewModel = accountViewModel ?: return null
val tagger =
NewMessageTagger(
message = message.text,
@@ -351,7 +344,7 @@ open class CommentPostViewModel :
val geoHash = (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString()
val emojis = findEmoji(tagger.message, account?.emoji?.myEmojis?.value)
val emojis = findEmoji(tagger.message, account.emoji.myEmojis.value)
val urls = findURLs(tagger.message)
val usedAttachments = iMetaAttachments.filterIsIn(urls.toSet())
@@ -432,7 +425,7 @@ open class CommentPostViewModel :
context: Context,
) = try {
uploadUnsafe(alt, contentWarningReason, mediaQuality, server, onError, context)
} catch (e: SignerExceptions.ReadOnlyException) {
} catch (_: SignerExceptions.ReadOnlyException) {
onError(
stringRes(context, R.string.read_only_user),
stringRes(context, R.string.login_with_a_private_key_to_be_able_to_sign_events),
@@ -448,7 +441,6 @@ open class CommentPostViewModel :
context: Context,
) {
viewModelScope.launch(Dispatchers.Default) {
val myAccount = account ?: return@launch
val myMultiOrchestrator = multiOrchestrator ?: return@launch
isUploadingImage = true
@@ -459,16 +451,16 @@ open class CommentPostViewModel :
contentWarningReason,
MediaCompressor.Companion.intToCompressorQuality(mediaQuality),
server,
myAccount,
account,
context,
)
if (results.allGood) {
results.successful.forEach { state ->
if (state.result is UploadOrchestrator.OrchestratorResult.NIP95Result) {
val nip95 = myAccount.createNip95(state.result.bytes, headerInfo = state.result.fileHeader, alt, contentWarningReason)
val nip95 = account.createNip95(state.result.bytes, headerInfo = state.result.fileHeader, alt, contentWarningReason)
nip95attachments = nip95attachments + nip95
val note = nip95.let { it1 -> account?.consumeNip95(it1.first, it1.second) }
val note = nip95.let { it1 -> account.consumeNip95(it1.first, it1.second) }
note?.let {
message = message.insertUrlAtCursor("nostr:" + it.toNEvent())
@@ -520,6 +512,8 @@ open class CommentPostViewModel :
}
open fun cancel() {
draftTag.rotate()
message = TextFieldValue("")
replyingTo = null
@@ -550,8 +544,6 @@ open class CommentPostViewModel :
iMetaAttachments.reset()
emojiSuggestions?.reset()
draftTag.rotate()
}
fun deleteMediaToUpload(selected: SelectedMediaProcessing) {
@@ -623,8 +615,7 @@ open class CommentPostViewModel :
iMetaAttachments.downloadAndPrepare(item.link.url) {
Amethyst.Companion.instance.okHttpClients
.getHttpClient(
accountViewModel?.account?.privacyState?.shouldUseTorForImageDownload(item.link.url)
?: false,
accountViewModel.account.privacyState.shouldUseTorForImageDownload(item.link.url),
)
}
}
@@ -662,7 +653,7 @@ open class CommentPostViewModel :
override fun updateZapFromText() {
viewModelScope.launch(Dispatchers.Default) {
val tagger =
NewMessageTagger(message.text, emptyList(), emptyList(), accountViewModel!!)
NewMessageTagger(message.text, emptyList(), emptyList(), accountViewModel)
tagger.run()
tagger.pTags?.forEach { taggedUser ->
if (!forwardZapTo.value.items.any { it.key == taggedUser }) {

View File

@@ -346,9 +346,10 @@ class ChatNewMessageViewModel :
}
suspend fun sendPostSync() {
val version = draftTag.current
innerSendPost(null)
accountViewModel.deleteDraft(draftTag.current)
cancel()
accountViewModel.deleteDraft(version)
}
suspend fun sendDraftSync() {
@@ -495,6 +496,8 @@ class ChatNewMessageViewModel :
}
fun cancel() {
draftTag.rotate()
message = TextFieldValue("")
subject = TextFieldValue("")
@@ -522,8 +525,6 @@ class ChatNewMessageViewModel :
iMetaAttachments.reset()
emojiSuggestions?.reset()
draftTag.rotate()
}
fun addToMessage(it: String) {

View File

@@ -48,7 +48,6 @@ import com.vitorpamplona.amethyst.service.location.LocationState
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator
import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
import com.vitorpamplona.amethyst.ui.note.creators.draftTags.DraftTagState
import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.EmojiSuggestionState
@@ -113,8 +112,8 @@ open class ChannelNewMessageViewModel :
}
}
var accountViewModel: AccountViewModel? = null
var account: Account? = null
lateinit var accountViewModel: AccountViewModel
lateinit var account: Account
var channel: Channel? = null
val replyTo = mutableStateOf<Note?>(null)
@@ -153,11 +152,11 @@ open class ChannelNewMessageViewModel :
var wantsZapraiser by mutableStateOf(false)
var zapRaiserAmount by mutableStateOf<Long?>(null)
fun lnAddress(): String? = account?.userProfile()?.info?.lnAddress()
fun lnAddress(): String? = account.userProfile().info?.lnAddress()
fun hasLnAddress(): Boolean = account?.userProfile()?.info?.lnAddress() != null
fun hasLnAddress(): Boolean = account.userProfile().info?.lnAddress() != null
fun user(): User? = account?.userProfile()
fun user(): User? = account.userProfile()
open fun init(accountVM: AccountViewModel) {
this.accountViewModel = accountVM
@@ -171,10 +170,7 @@ open class ChannelNewMessageViewModel :
this.emojiSuggestions?.reset()
this.emojiSuggestions = EmojiSuggestionState(accountVM)
this.uploadState =
ChatFileUploadState(
account?.settings?.defaultFileServer ?: DEFAULT_MEDIA_SERVERS[0],
)
this.uploadState = ChatFileUploadState(account.settings.defaultFileServer)
}
open fun load(channel: Channel) {
@@ -197,7 +193,7 @@ open class ChannelNewMessageViewModel :
if (noteEvent is DraftEvent && noteAuthor != null) {
viewModelScope.launch(Dispatchers.IO) {
accountViewModel?.createTempDraftNote(noteEvent)?.let { innerNote ->
accountViewModel.createTempDraftNote(noteEvent)?.let { innerNote ->
val oldTag = (draft.event as? AddressableEvent)?.dTag()
if (oldTag != null) {
draftTag.set(oldTag)
@@ -243,14 +239,14 @@ open class ChannelNewMessageViewModel :
if (draftEvent as? ChannelMessageEvent != null) {
val replyId = draftEvent.reply()?.eventId
if (replyId != null) {
accountViewModel?.checkGetOrCreateNote(replyId) {
accountViewModel.checkGetOrCreateNote(replyId) {
replyTo.value = it
}
}
} else if (draftEvent as? LiveActivitiesChatMessageEvent != null) {
val replyId = draftEvent.reply()?.eventId
if (replyId != null) {
accountViewModel?.checkGetOrCreateNote(replyId) {
accountViewModel.checkGetOrCreateNote(replyId) {
replyTo.value = it
}
}
@@ -274,18 +270,16 @@ open class ChannelNewMessageViewModel :
val template = createTemplate() ?: return
val channelRelays = channel?.relays() ?: emptySet()
accountViewModel?.account?.signAndSendPrivately(template, channelRelays)
accountViewModel?.deleteDraft(draftTag.current)
val version = draftTag.current
cancel()
accountViewModel.account.signAndSendPrivately(template, channelRelays)
accountViewModel.deleteDraft(version)
}
suspend fun sendDraftSync() {
val accountViewModel = accountViewModel ?: return
if (message.text.isBlank()) {
account?.deleteDraft(draftTag.current)
account.deleteDraft(draftTag.current)
} else {
val template = createTemplate() ?: return
accountViewModel.account.createAndSendDraft(draftTag.current, template)
@@ -302,7 +296,7 @@ open class ChannelNewMessageViewModel :
onceUploaded: () -> Unit,
) = try {
uploadUnsafe(onError, context, onceUploaded)
} catch (e: SignerExceptions.ReadOnlyException) {
} catch (_: SignerExceptions.ReadOnlyException) {
onError(
stringRes(context, R.string.read_only_user),
stringRes(context, R.string.login_with_a_private_key_to_be_able_to_sign_events),
@@ -315,7 +309,6 @@ open class ChannelNewMessageViewModel :
onceUploaded: () -> Unit,
) {
viewModelScope.launch(Dispatchers.Default) {
val myAccount = account ?: return@launch
val uploadState = uploadState ?: return@launch
val myMultiOrchestrator = uploadState.multiOrchestrator ?: return@launch
@@ -328,26 +321,26 @@ open class ChannelNewMessageViewModel :
uploadState.contentWarningReason,
MediaCompressor.intToCompressorQuality(uploadState.mediaQualitySlider),
uploadState.selectedServer,
myAccount,
account,
context,
)
if (results.allGood) {
results.successful.forEach {
if (it.result is UploadOrchestrator.OrchestratorResult.NIP95Result) {
val nip95 = myAccount.createNip95(it.result.bytes, headerInfo = it.result.fileHeader, uploadState.caption, uploadState.contentWarningReason)
results.successful.forEach { upload ->
if (upload.result is UploadOrchestrator.OrchestratorResult.NIP95Result) {
val nip95 = account.createNip95(upload.result.bytes, headerInfo = upload.result.fileHeader, uploadState.caption, uploadState.contentWarningReason)
nip95attachments = nip95attachments + nip95
val note = nip95.let { it1 -> account?.consumeNip95(it1.first, it1.second) }
val note = nip95.let { it1 -> account.consumeNip95(it1.first, it1.second) }
note?.let {
message = message.insertUrlAtCursor(it.toNostrUri())
}
urlPreview = findUrlInMessage()
} else if (it.result is UploadOrchestrator.OrchestratorResult.ServerResult) {
iMetaAttachments.add(it.result, uploadState.caption, uploadState.contentWarningReason)
} else if (upload.result is UploadOrchestrator.OrchestratorResult.ServerResult) {
iMetaAttachments.add(upload.result, uploadState.caption, uploadState.contentWarningReason)
message = message.insertUrlAtCursor(it.result.url)
message = message.insertUrlAtCursor(upload.result.url)
urlPreview = findUrlInMessage()
}
}
@@ -367,8 +360,6 @@ open class ChannelNewMessageViewModel :
private suspend fun createTemplate(): EventTemplate<out Event>? {
val channel = channel ?: return null
val accountViewModel = accountViewModel ?: return null
val tagger =
NewMessageTagger(
message = message.text,
@@ -442,7 +433,7 @@ open class ChannelNewMessageViewModel :
imetas(usedAttachments)
}
} else if (activity != null) {
val hint = EventHintBundle(activity, channelRelays.firstOrNull() ?: replyingToEvent?.relay)
val hint = EventHintBundle(activity, channelRelays.firstOrNull())
LiveActivitiesChatMessageEvent.message(tagger.message, hint) {
hashtags(findHashtags(tagger.message))
@@ -491,6 +482,8 @@ open class ChannelNewMessageViewModel :
}
open fun cancel() {
draftTag.rotate()
message = TextFieldValue("")
replyTo.value = null
@@ -515,14 +508,6 @@ open class ChannelNewMessageViewModel :
iMetaAttachments.reset()
emojiSuggestions?.reset()
draftTag.rotate()
}
fun deleteDraft() {
viewModelScope.launch(Dispatchers.IO) {
accountViewModel?.deleteDraft(draftTag.current)
}
}
open fun findUrlInMessage(): String? = RichTextParser().parseValidUrls(message.text).firstOrNull()
@@ -586,10 +571,11 @@ open class ChannelNewMessageViewModel :
val wordToInsert = item.link.url + " "
viewModelScope.launch(Dispatchers.IO) {
iMetaAttachments.downloadAndPrepare(
item.link.url,
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.privacyState?.shouldUseTorForImageDownload(item.link.url) ?: false) },
)
iMetaAttachments.downloadAndPrepare(item.link.url) {
Amethyst.instance.okHttpClients.getHttpClient(
accountViewModel.account.privacyState.shouldUseTorForImageDownload(item.link.url),
)
}
}
message = message.replaceCurrentWord(wordToInsert)
@@ -636,7 +622,7 @@ open class ChannelNewMessageViewModel :
fun updateZapFromText() {
viewModelScope.launch(Dispatchers.Default) {
val tagger = NewMessageTagger(message.text, emptyList(), emptyList(), accountViewModel!!)
val tagger = NewMessageTagger(message.text, emptyList(), emptyList(), accountViewModel)
tagger.run()
tagger.pTags?.forEach { taggedUser ->
if (!forwardZapTo.items.any { it.key == taggedUser }) {

View File

@@ -431,6 +431,8 @@ open class NewProductViewModel :
}
open fun cancel() {
draftTag.rotate()
message = TextFieldValue("")
multiOrchestrator = null
@@ -465,8 +467,6 @@ open class NewProductViewModel :
emojiSuggestions?.reset()
reloadRelaySet()
draftTag.rotate()
}
fun reloadRelaySet() {

View File

@@ -475,11 +475,11 @@ open class ShortNotePostViewModel :
}
}
accountViewModel.account.signAndComputeBroadcast(template, extraNotesToBroadcast)
accountViewModel.deleteDraft(draftTag.current)
val version = draftTag.current
cancel()
accountViewModel.account.signAndComputeBroadcast(template, extraNotesToBroadcast)
accountViewModel.deleteDraft(version)
}
suspend fun sendDraftSync() {
@@ -586,7 +586,7 @@ open class ShortNotePostViewModel :
context: Context,
) = try {
uploadUnsafe(alt, contentWarningReason, mediaQuality, server, onError, context)
} catch (e: SignerExceptions.ReadOnlyException) {
} catch (_: SignerExceptions.ReadOnlyException) {
onError(
stringRes(context, R.string.read_only_user),
stringRes(context, R.string.login_with_a_private_key_to_be_able_to_sign_events),
@@ -664,6 +664,8 @@ open class ShortNotePostViewModel :
}
open fun cancel() {
draftTag.rotate()
message = TextFieldValue("")
forkedFromNote = null
@@ -701,8 +703,6 @@ open class ShortNotePostViewModel :
iMetaAttachments.reset()
emojiSuggestions?.reset()
draftTag.rotate()
}
fun deleteMediaToUpload(selected: SelectedMediaProcessing) {

View File

@@ -309,10 +309,11 @@ class NewPublicMessageViewModel :
}
}
accountViewModel.account.signAndComputeBroadcast(template, extraNotesToBroadcast)
accountViewModel.deleteDraft(draftTag.current)
val version = draftTag.current
cancel()
accountViewModel.account.signAndComputeBroadcast(template, extraNotesToBroadcast)
accountViewModel.deleteDraft(version)
}
suspend fun sendDraftSync() {
@@ -467,6 +468,8 @@ class NewPublicMessageViewModel :
}
fun cancel() {
draftTag.rotate()
toUsers = TextFieldValue("")
message = TextFieldValue("")
multiOrchestrator = null
@@ -491,8 +494,6 @@ class NewPublicMessageViewModel :
iMetaAttachments.reset()
emojiSuggestions?.reset()
draftTag.rotate()
}
fun deleteMediaToUpload(selected: SelectedMediaProcessing) {