Replace nip19 nostr profile mentions with the profile names.

This commit is contained in:
Kgothatso Ngako
2026-05-02 20:19:33 +02:00
parent 5256a4719b
commit eacfdd979b
3 changed files with 48 additions and 10 deletions

View File

@@ -2,19 +2,20 @@ package ac.cord.auxiliary.compose.ui.composable
import ac.cord.auxiliary.compose.database.model.NostrEvent import ac.cord.auxiliary.compose.database.model.NostrEvent
import ac.cord.auxiliary.compose.database.model.Profile import ac.cord.auxiliary.compose.database.model.Profile
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalMention
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent import ac.cord.auxiliary.compose.database.model.intermdiate.LocalNostrEvent
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowing import ac.cord.auxiliary.compose.database.model.intermdiate.LocalProfileWithFollowing
import ac.cord.auxiliary.compose.extensions.toFormattedTimeAndDateString import ac.cord.auxiliary.compose.extensions.toFormattedTimeAndDateString
import ac.cord.auxiliary.compose.repository.NostrRepository import ac.cord.auxiliary.compose.repository.NostrRepository
import ac.cord.auxiliary.compose.ui.composable.widgets.LoadingDataIndicator import ac.cord.auxiliary.compose.ui.composable.widgets.LoadingDataIndicator
import ac.cord.auxiliary.compose.ui.composable.widgets.detail.TextNoteEventDetail import ac.cord.auxiliary.compose.ui.composable.widgets.content.ContentParser
import ac.cord.auxiliary.compose.ui.composable.widgets.content.ContentSegment
import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileAvatar import ac.cord.auxiliary.compose.ui.composable.widgets.profile.ProfileAvatar
import ac.cord.auxiliary.compose.ui.theme.AuxTheme import ac.cord.auxiliary.compose.ui.theme.AuxTheme
import ac.cord.auxiliary.compose.ui.view.model.WriteNewNoteViewModel import ac.cord.auxiliary.compose.ui.view.model.WriteNewNoteViewModel
import ac.cord.auxiliary.compose.ui.view.state.WriteNewNoteUIState import ac.cord.auxiliary.compose.ui.view.state.WriteNewNoteUIState
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@@ -23,16 +24,14 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.input.OutputTransformation
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.NavigateNext import androidx.compose.material.icons.filled.NavigateNext
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LoadingIndicator import androidx.compose.material3.LoadingIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@@ -46,14 +45,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import kotlin.time.Clock import kotlin.time.Clock
import kotlin.time.Instant
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@@ -126,6 +123,8 @@ fun WriteNewNoteScreen(
) )
} }
val mentionedProfiles: List<LocalMention> = emptyList()
TextField( TextField(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
state = writeNewNoteViewModel.writeNewNoteFormState.textField.textFieldState, state = writeNewNoteViewModel.writeNewNoteFormState.textField.textFieldState,
@@ -166,6 +165,26 @@ fun WriteNewNoteScreen(
profile = writeNewNoteUIState.localProfile.profile, profile = writeNewNoteUIState.localProfile.profile,
size = 35.dp size = 35.dp
) )
},
outputTransformation = OutputTransformation {
val content = originalText.toString()
val nostrProfiles = ContentParser.parse(content.trimEnd('\n', '\r'), emptyMap(), emptyMap(), trimBlankLines = true)
.filterIsInstance<ContentSegment.NostrProfileSegment>()
// TODO: We might want to order these profiles by the startIndex so that we can replace the better
if (mentionedProfiles.isNotEmpty()) {
for (nostrProfile in nostrProfiles) {
mentionedProfiles.find { mentionedProfile -> mentionedProfile.profile?.publicKey == nostrProfile.pubkey }?.let { mentionedProfile ->
val profileMention = mentionedProfile.profile?.humanReadableNameOrPubkey() ?: "${nostrProfile.pubkey.take(8)}...${nostrProfile.pubkey.takeLast(4)}"
replace(
start = nostrProfile.startIndex,
end = nostrProfile.endIndex,
text = "@$profileMention"
)
}
}
}
} }
) )

View File

@@ -55,16 +55,30 @@ internal object ContentParser {
else -> segments.add(ContentSegment.InlineLinkSegment(url)) else -> segments.add(ContentSegment.InlineLinkSegment(url))
} }
}else if (nip19ParseResult.isNotEmpty()) { }else if (nip19ParseResult.isNotEmpty()) {
Logger.d("Range: ${match.range}")
Logger.d("Nip19Result: $nip19ParseResult") Logger.d("Nip19Result: $nip19ParseResult")
when (val entity = nip19ParseResult.first()) { when (val entity = nip19ParseResult.first()) {
is NAddress -> { is NAddress -> {
segments.add(ContentSegment.NostrAddressableSegment(entity.dTag, entity.relay.map { it.url }, entity.author, entity.kind)) segments.add(ContentSegment.NostrAddressableSegment(entity.dTag, entity.relay.map { it.url }, entity.author, entity.kind))
} }
is NProfile -> { is NProfile -> {
segments.add(ContentSegment.NostrProfileSegment(entity.hex, entity.relay.map { it.url })) segments.add(
ContentSegment.NostrProfileSegment(
pubkey = entity.hex,
relayHints = entity.relay.map { it.url },
startIndex = match.range.first,
endIndex = match.range.last
)
)
} }
is NPub -> { is NPub -> {
segments.add(ContentSegment.NostrProfileSegment(pubkey = entity.hex)) segments.add(
ContentSegment.NostrProfileSegment(
pubkey = entity.hex,
startIndex = match.range.first,
endIndex = match.range.last
)
)
} }
is NNote -> { is NNote -> {
segments.add(ContentSegment.NostrNoteSegment(entity.hex)) segments.add(ContentSegment.NostrNoteSegment(entity.hex))

View File

@@ -53,7 +53,12 @@ internal sealed interface ContentSegment {
) )
} }
} }
data class NostrProfileSegment(val pubkey: String, val relayHints: List<String> = emptyList()) : ContentSegment { data class NostrProfileSegment(
val pubkey: String,
val relayHints: List<String> = emptyList(),
val startIndex: Int,
val endIndex: Int
) : ContentSegment {
override fun toString(): String { override fun toString(): String {
return NProfile.create( return NProfile.create(
authorPubKeyHex = pubkey, authorPubKeyHex = pubkey,