code review fixes:

textHint variable was retrieved but never used
extracted logic for fixing relay hints and author information in tags
made mimeType check logic clearer
This commit is contained in:
davotoula
2026-01-11 18:34:59 +01:00
parent f51d0fb56e
commit 1e898d57b2
4 changed files with 36 additions and 113 deletions
@@ -301,7 +301,7 @@ fun Event.getAudioMetaWithWaveform(): AudioMeta? {
val audioMetas = imetas().map { AudioMeta.parse(it) } val audioMetas = imetas().map { AudioMeta.parse(it) }
return audioMetas.firstOrNull { meta -> return audioMetas.firstOrNull { meta ->
meta.waveform != null && meta.waveform != null &&
meta.mimeType?.startsWith("audio/") != false (meta.mimeType == null || meta.mimeType?.startsWith("audio/") == true)
} }
} }
@@ -28,7 +28,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.core.net.toUri
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@@ -67,12 +66,8 @@ import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
import com.vitorpamplona.amethyst.service.location.LocationState import com.vitorpamplona.amethyst.service.location.LocationState
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.RelaySubscriptionsCoordinator import com.vitorpamplona.amethyst.service.relayClient.reqCommand.RelaySubscriptionsCoordinator
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCPaymentFilterAssembler import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCPaymentFilterAssembler
import com.vitorpamplona.amethyst.service.uploads.CompressorQuality
import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator
import com.vitorpamplona.amethyst.service.uploads.UploadingState
import com.vitorpamplona.amethyst.ui.actions.Dao import com.vitorpamplona.amethyst.ui.actions.Dao
import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk
import com.vitorpamplona.amethyst.ui.actions.uploads.RecordingResult
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
import com.vitorpamplona.amethyst.ui.components.toasts.ToastManager import com.vitorpamplona.amethyst.ui.components.toasts.ToastManager
import com.vitorpamplona.amethyst.ui.feeds.FeedState import com.vitorpamplona.amethyst.ui.feeds.FeedState
@@ -105,13 +100,9 @@ import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions
import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
import com.vitorpamplona.quartz.nip01Core.tags.people.toPTag
import com.vitorpamplona.quartz.nip03Timestamp.EmptyOtsResolverBuilder import com.vitorpamplona.quartz.nip03Timestamp.EmptyOtsResolverBuilder
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag
import com.vitorpamplona.quartz.nip10Notes.tags.markedETags
import com.vitorpamplona.quartz.nip10Notes.tags.notify
import com.vitorpamplona.quartz.nip10Notes.tags.prepareETagsAsReplyTo
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
@@ -139,8 +130,6 @@ import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
import com.vitorpamplona.quartz.nipA0VoiceMessages.AudioMeta
import com.vitorpamplona.quartz.nipA0VoiceMessages.BaseVoiceEvent
import com.vitorpamplona.quartz.utils.Hex import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.Log import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.TimeUtils import com.vitorpamplona.quartz.utils.TimeUtils
@@ -1004,6 +993,32 @@ class AccountViewModel(
fun getNoteIfExists(hex: HexKey): Note? = LocalCache.getNoteIfExists(hex) fun getNoteIfExists(hex: HexKey): Note? = LocalCache.getNoteIfExists(hex)
/**
* Fixes author and relay hints in MarkedETag list by looking up notes from cache.
* This ensures reply tags have proper author pubkeys and relay hints for threading.
*/
fun fixReplyTagHints(tags: List<MarkedETag>) {
tags.forEach { tag ->
val note = getNoteIfExists(tag.eventId)
val cachedAuthor = note?.author?.pubkeyHex
val cachedRelay = note?.relayHintUrl()
// Fix author if missing or different from cached
if (tag.author.isNullOrBlank() && cachedAuthor != null) {
tag.author = cachedAuthor
} else if (cachedAuthor != null && tag.author != cachedAuthor) {
tag.author = cachedAuthor
}
// Fix relay hint if missing or different from cached
if (tag.relay == null && cachedRelay != null) {
tag.relay = cachedRelay
} else if (cachedRelay != null && tag.relay != cachedRelay) {
tag.relay = cachedRelay
}
}
}
override suspend fun getOrCreateAddressableNote(address: Address): AddressableNote = LocalCache.getOrCreateAddressableNote(address) override suspend fun getOrCreateAddressableNote(address: Address): AddressableNote = LocalCache.getOrCreateAddressableNote(address)
fun getAddressableNoteIfExists(key: String): AddressableNote? = LocalCache.getAddressableNoteIfExists(key) fun getAddressableNoteIfExists(key: String): AddressableNote? = LocalCache.getAddressableNoteIfExists(key)
@@ -1154,79 +1169,6 @@ class AccountViewModel(
super.onCleared() super.onCleared()
} }
fun sendVoiceReply(
note: Note,
recording: RecordingResult,
context: Context,
) {
if (isWriteable()) {
launchSigner {
val uploader = UploadOrchestrator()
val result =
uploader.upload(
uri = recording.file.toUri(),
mimeType = recording.mimeType,
alt = null,
contentWarningReason = null,
compressionQuality = CompressorQuality.UNCOMPRESSED,
server = account.settings.defaultFileServer,
account = account,
context = context,
)
if (result is UploadingState.Finished && result.result is UploadOrchestrator.OrchestratorResult.ServerResult) {
val audioMeta =
AudioMeta(
url = result.result.url,
mimeType = result.result.fileHeader.mimeType ?: recording.mimeType,
hash = result.result.fileHeader.hash,
duration = recording.duration,
waveform = recording.amplitudes,
)
// Check if replying to a voice event
val voiceHint = note.toEventHint<BaseVoiceEvent>()
if (voiceHint != null) {
// Create VoiceReplyEvent (KIND 1244) for voice-to-voice replies
account.sendVoiceReplyMessage(
result.result.url,
result.result.fileHeader.mimeType ?: recording.mimeType,
result.result.fileHeader.hash,
recording.duration,
recording.amplitudes,
voiceHint,
)
} else {
// Create TextNoteEvent (KIND 1) with audio IMeta for voice replies to regular notes
val template =
TextNoteEvent.build(audioMeta.url) {
val replyingTo = note.toEventHint<TextNoteEvent>()
if (replyingTo != null) {
val tags = prepareETagsAsReplyTo(replyingTo, null)
markedETags(tags)
notify(replyingTo.toPTag())
}
// Add audio as IMeta attachment
add(audioMeta.toIMetaArray())
}
account.signAndComputeBroadcast(template)
}
} else if (result is UploadingState.Error) {
toastManager.toast(
R.string.failed_to_upload_media_no_details,
result.errorResource,
*result.params,
)
}
}
} else {
toastManager.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_reply,
)
}
}
fun loadThumb( fun loadThumb(
context: Context, context: Context,
thumbUri: String, thumbUri: String,
@@ -83,6 +83,7 @@ import com.vitorpamplona.quartz.nip01Core.tags.geohash.geohash
import com.vitorpamplona.quartz.nip01Core.tags.geohash.getGeoHash import com.vitorpamplona.quartz.nip01Core.tags.geohash.getGeoHash
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
import com.vitorpamplona.quartz.nip01Core.tags.people.pTags import com.vitorpamplona.quartz.nip01Core.tags.people.pTags
import com.vitorpamplona.quartz.nip01Core.tags.people.toPTag
import com.vitorpamplona.quartz.nip01Core.tags.references.references import com.vitorpamplona.quartz.nip01Core.tags.references.references
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
@@ -561,26 +562,9 @@ open class ShortNotePostViewModel :
val replyingTo = originalNote?.toEventHint<TextNoteEvent>() val replyingTo = originalNote?.toEventHint<TextNoteEvent>()
if (replyingTo != null) { if (replyingTo != null) {
val tags = prepareETagsAsReplyTo(replyingTo, null) val tags = prepareETagsAsReplyTo(replyingTo, null)
tags.forEach { accountViewModel.fixReplyTagHints(tags)
val note = accountViewModel.getNoteIfExists(it.eventId)
val ourAuthor = note?.author?.pubkeyHex
val ourHint = note?.relayHintUrl()
if (it.author == null || it.author?.isBlank() == true) {
it.author = ourAuthor
} else {
if (ourAuthor != null && it.author != ourAuthor) {
it.author = ourAuthor
}
}
if (it.relay == null) {
it.relay = ourHint
} else {
if (ourHint != null && it.relay != ourHint) {
it.relay = ourHint
}
}
}
markedETags(tags) markedETags(tags)
notify(replyingTo.toPTag())
} }
pTags?.let { userList -> pTags?.let { userList ->
val tags = val tags =
@@ -37,7 +37,6 @@ import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
import com.vitorpamplona.amethyst.ui.actions.uploads.RecordingResult import com.vitorpamplona.amethyst.ui.actions.uploads.RecordingResult
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.tags.people.toPTag import com.vitorpamplona.quartz.nip01Core.tags.people.toPTag
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip10Notes.tags.markedETags import com.vitorpamplona.quartz.nip10Notes.tags.markedETags
@@ -222,7 +221,7 @@ class VoiceReplyViewModel : ViewModel() {
accountViewModel.account.signAndComputeBroadcast(VoiceReplyEvent.build(audioMeta, voiceHint)) accountViewModel.account.signAndComputeBroadcast(VoiceReplyEvent.build(audioMeta, voiceHint))
} else { } else {
// Create TextNoteEvent (KIND 1) with audio IMeta for voice replies to regular notes // Create TextNoteEvent (KIND 1) with audio IMeta for voice replies to regular notes
val textHint = note.toEventHint<Event>() val textHint = note.toEventHint<TextNoteEvent>()
if (textHint == null) { if (textHint == null) {
accountViewModel.toastManager.toast(uploadErrorTitle, uploadVoiceFailed) accountViewModel.toastManager.toast(uploadErrorTitle, uploadVoiceFailed)
return return
@@ -230,12 +229,10 @@ class VoiceReplyViewModel : ViewModel() {
val template = val template =
TextNoteEvent.build(audioMeta.url) { TextNoteEvent.build(audioMeta.url) {
val replyingTo = note.toEventHint<TextNoteEvent>() val tags = prepareETagsAsReplyTo(textHint, null)
if (replyingTo != null) { accountViewModel.fixReplyTagHints(tags)
val tags = prepareETagsAsReplyTo(replyingTo, null)
markedETags(tags) markedETags(tags)
notify(replyingTo.toPTag()) notify(textHint.toPTag())
}
// Add audio as IMeta attachment // Add audio as IMeta attachment
add(audioMeta.toIMetaArray()) add(audioMeta.toIMetaArray())
} }