Format last files with conflicts

This commit is contained in:
Chemaclass
2023-03-07 20:43:34 +01:00
parent f1b6927bb3
commit c9b859610e
14 changed files with 1673 additions and 1697 deletions

View File

@@ -14,27 +14,21 @@ import com.vitorpamplona.amethyst.service.model.ChannelHideMessageEvent
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
import com.vitorpamplona.amethyst.service.model.ChannelMuteUserEvent
import com.vitorpamplona.amethyst.service.model.LnZapEvent
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
import com.vitorpamplona.amethyst.service.model.ReactionEvent
import com.vitorpamplona.amethyst.service.model.ReportEvent
import com.vitorpamplona.amethyst.service.model.RepostEvent
import com.vitorpamplona.amethyst.service.model.ContactListEvent
import com.vitorpamplona.amethyst.service.model.DeletionEvent
import com.vitorpamplona.amethyst.service.model.Event
import com.vitorpamplona.amethyst.service.model.LnZapEvent
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
import com.vitorpamplona.amethyst.service.model.MetadataEvent
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
import com.vitorpamplona.amethyst.service.model.ReactionEvent
import com.vitorpamplona.amethyst.service.model.RecommendRelayEvent
import com.vitorpamplona.amethyst.service.model.ReportEvent
import com.vitorpamplona.amethyst.service.model.RepostEvent
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
import com.vitorpamplona.amethyst.service.relays.Relay
import fr.acinq.secp256k1.Hex
import java.io.ByteArrayInputStream
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -43,7 +37,12 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import nostr.postr.toNpub
import java.io.ByteArrayInputStream
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean
object LocalCache {
val metadataParser = jacksonObjectMapper()
@@ -108,7 +107,6 @@ object LocalCache {
}
}
@Synchronized
fun getOrCreateChannel(key: String): Channel {
return channels[key] ?: run {
@@ -121,10 +119,11 @@ object LocalCache {
fun checkGetOrCreateAddressableNote(key: String): AddressableNote? {
return try {
val addr = ATag.parse(key, null) // relay doesn't matter for the index.
if (addr != null)
if (addr != null) {
getOrCreateAddressableNote(addr)
else
} else {
null
}
} catch (e: IllegalArgumentException) {
Log.e("LocalCache", "Invalid Key to create channel: $key", e)
null
@@ -170,7 +169,6 @@ object LocalCache {
.format(DateTimeFormatter.ofPattern("uuuu MMM d hh:mm a"))
}
fun consume(event: TextNoteEvent, relay: Relay? = null) {
val note = getOrCreateNote(event.id)
val author = getOrCreateUser(event.pubKey)
@@ -332,7 +330,6 @@ object LocalCache {
citations.add(tag[1])
}
} catch (e: Exception) {
}
}
return citations
@@ -547,8 +544,8 @@ object LocalCache {
}
}
if (event.content == "!" // nostr_console hide.
|| event.content == "\u26A0\uFE0F" // Warning sign
if (event.content == "!" || // nostr_console hide.
event.content == "\u26A0\uFE0F" // Warning sign
) {
// Counts the replies
repliesTo.forEach {
@@ -683,11 +680,9 @@ object LocalCache {
}
fun consume(event: ChannelHideMessageEvent) {
}
fun consume(event: ChannelMuteUserEvent) {
}
fun consume(event: LnZapEvent) {
@@ -762,31 +757,31 @@ object LocalCache {
fun findUsersStartingWith(username: String): List<User> {
return users.values.filter {
(it.anyNameStartsWith(username))
|| it.pubkeyHex.startsWith(username, true)
|| it.pubkeyNpub().startsWith(username, true)
(it.anyNameStartsWith(username)) ||
it.pubkeyHex.startsWith(username, true) ||
it.pubkeyNpub().startsWith(username, true)
}
}
fun findNotesStartingWith(text: String): List<Note> {
return notes.values.filter {
(it.event is TextNoteEvent && it.event?.content()?.contains(text, true) ?: false)
|| (it.event is ChannelMessageEvent && it.event?.content()?.contains(text, true) ?: false)
|| it.idHex.startsWith(text, true)
|| it.idNote().startsWith(text, true)
(it.event is TextNoteEvent && it.event?.content()?.contains(text, true) ?: false) ||
(it.event is ChannelMessageEvent && it.event?.content()?.contains(text, true) ?: false) ||
it.idHex.startsWith(text, true) ||
it.idNote().startsWith(text, true)
} + addressables.values.filter {
(it.event as? LongTextNoteEvent)?.content?.contains(text, true) ?: false
|| (it.event as? LongTextNoteEvent)?.title()?.contains(text, true) ?: false
|| (it.event as? LongTextNoteEvent)?.summary()?.contains(text, true) ?: false
|| it.idHex.startsWith(text, true)
(it.event as? LongTextNoteEvent)?.content?.contains(text, true) ?: false ||
(it.event as? LongTextNoteEvent)?.title()?.contains(text, true) ?: false ||
(it.event as? LongTextNoteEvent)?.summary()?.contains(text, true) ?: false ||
it.idHex.startsWith(text, true)
}
}
fun findChannelsStartingWith(text: String): List<Channel> {
return channels.values.filter {
it.anyNameStartsWith(text)
|| it.idHex.startsWith(text, true)
|| it.idNote().startsWith(text, true)
it.anyNameStartsWith(text) ||
it.idHex.startsWith(text, true) ||
it.idNote().startsWith(text, true)
}
}
@@ -930,6 +925,4 @@ class LocalCacheLiveData(val cache: LocalCache): LiveData<LocalCacheState>(Local
}
}
class LocalCacheState(val cache: LocalCache) {
}
class LocalCacheState(val cache: LocalCache)

View File

@@ -6,13 +6,6 @@ import com.vitorpamplona.amethyst.service.model.*
import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.ui.note.toShortenHex
import fr.acinq.secp256k1.Hex
import java.math.BigDecimal
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Date
import java.util.concurrent.atomic.AtomicBoolean
import java.util.regex.Pattern
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -20,10 +13,16 @@ import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.math.BigDecimal
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Date
import java.util.concurrent.atomic.AtomicBoolean
import java.util.regex.Pattern
val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]")
class AddressableNote(val address: ATag) : Note(address.toTag()) {
override fun idNote() = address.toNAddr()
override fun idDisplayNote() = idNote().toShortenHex()
@@ -62,9 +61,9 @@ open class Note(val idHex: String) {
fun channel(): Channel? {
val channelHex =
(event as? ChannelMessageEvent)?.channel() ?:
(event as? ChannelMetadataEvent)?.channel() ?:
(event as? ChannelCreateEvent)?.let { it.id }
(event as? ChannelMessageEvent)?.channel()
?: (event as? ChannelMetadataEvent)?.channel()
?: (event as? ChannelCreateEvent)?.let { it.id }
return channelHex?.let { LocalCache.checkGetOrCreateChannel(it) }
}
@@ -156,7 +155,6 @@ open class Note(val idHex: String) {
}
}
fun addBoost(note: Note) {
if (note !in boosts) {
boosts = boosts + note
@@ -242,9 +240,11 @@ open class Note(val idHex: String) {
fun hasAnyReports(): Boolean {
val dayAgo = Date().time / 1000 - 24 * 60 * 60
return reports.isNotEmpty() ||
(author?.reports?.values?.filter {
(
author?.reports?.values?.filter {
it.firstOrNull { (it.createdAt() ?: 0) > dayAgo } != null
}?.isNotEmpty() ?: false)
}?.isNotEmpty() ?: false
)
}
fun directlyCiteUsersHex(): Set<HexKey> {
@@ -257,7 +257,6 @@ open class Note(val idHex: String) {
returningList.add(tag[1])
}
} catch (e: Exception) {
}
}
return returningList
@@ -275,17 +274,16 @@ open class Note(val idHex: String) {
}
}
} catch (e: Exception) {
}
}
return returningList
}
fun directlyCites(userProfile: User): Boolean {
return author == userProfile
|| (userProfile in directlyCiteUsers())
|| (event is ReactionEvent && replyTo?.lastOrNull()?.directlyCites(userProfile) == true)
|| (event is RepostEvent && replyTo?.lastOrNull()?.directlyCites(userProfile) == true)
return author == userProfile ||
(userProfile in directlyCiteUsers()) ||
(event is ReactionEvent && replyTo?.lastOrNull()?.directlyCites(userProfile) == true) ||
(event is RepostEvent && replyTo?.lastOrNull()?.directlyCites(userProfile) == true)
}
fun isNewThread(): Boolean {
@@ -329,7 +327,6 @@ open class Note(val idHex: String) {
}
}
class NoteLiveSet(u: Note) {
// Observers line up here.
val metadata: NoteLiveData = NoteLiveData(u)
@@ -342,13 +339,13 @@ class NoteLiveSet(u: Note) {
val zaps: NoteLiveData = NoteLiveData(u)
fun isInUse(): Boolean {
return metadata.hasObservers()
|| reactions.hasObservers()
|| boosts.hasObservers()
|| replies.hasObservers()
|| reports.hasObservers()
|| relays.hasObservers()
|| zaps.hasObservers()
return metadata.hasObservers() ||
reactions.hasObservers() ||
boosts.hasObservers() ||
replies.hasObservers() ||
reports.hasObservers() ||
relays.hasObservers() ||
zaps.hasObservers()
}
}
@@ -384,7 +381,6 @@ class NoteLiveData(val note: Note): LiveData<NoteState>(NoteState(note)) {
} else {
NostrSingleEventDataSource.add(note)
}
}
override fun onInactive() {

View File

@@ -19,10 +19,11 @@ class ThreadAssembler {
// recursive
val roots = note.replyTo?.map {
if (it !in testedNotes)
if (it !in testedNotes) {
searchRoot(it, testedNotes)
else
} else {
null
}
}?.filterNotNull()
if (roots != null && roots.isNotEmpty()) {
@@ -37,15 +38,15 @@ class ThreadAssembler {
val (result, elapsed) = measureTimedValue {
val note = if (noteId.contains(":")) {
val aTag = ATag.parse(noteId, null)
if (aTag != null)
if (aTag != null) {
LocalCache.getOrCreateAddressableNote(aTag)
else
} else {
return emptySet()
}
} else {
LocalCache.getOrCreateNote(noteId)
}
if (note.event != null) {
val thread = mutableSetOf<Note>()
@@ -59,7 +60,7 @@ class ThreadAssembler {
}
}
println("Model Refresh: Thread loaded in ${elapsed}")
println("Model Refresh: Thread loaded in $elapsed")
return result
}

View File

@@ -32,7 +32,7 @@ class Nip19 {
return naddr(bytes)
}
} catch (e: Throwable) {
println("Issue trying to Decode NIP19 ${uri}: ${e.message}")
println("Issue trying to Decode NIP19 $uri: ${e.message}")
}
return null
@@ -43,7 +43,7 @@ class Nip19 {
}
private fun note(bytes: ByteArray): Return {
return Return(Type.NOTE, bytes.toHexKey(), null);
return Return(Type.NOTE, bytes.toHexKey(), null)
}
private fun nprofile(bytes: ByteArray): Return? {

View File

@@ -4,14 +4,14 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.model.BadgeAwardEvent
import com.vitorpamplona.amethyst.service.model.BadgeProfilesEvent
import com.vitorpamplona.amethyst.service.model.ContactListEvent
import com.vitorpamplona.amethyst.service.model.LnZapEvent
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.service.relays.TypedFilter
import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.model.ContactListEvent
import com.vitorpamplona.amethyst.service.model.MetadataEvent
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.TypedFilter
object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") {
var user: User? = null

View File

@@ -23,8 +23,9 @@ data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String, val rela
var fullArray =
byteArrayOf(NIP19TLVTypes.SPECIAL.id, dTag.size.toByte()) + dTag
if (relay != null)
if (relay != null) {
fullArray = fullArray + byteArrayOf(NIP19TLVTypes.RELAY.id, relay.size.toByte()) + relay
}
fullArray = fullArray +
byteArrayOf(NIP19TLVTypes.AUTHOR.id, author.size.toByte()) + author +
@@ -39,11 +40,12 @@ data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String, val rela
}
fun parse(address: String, relay: String?): ATag? {
return if (address.startsWith("naddr") || address.startsWith("nostr:naddr"))
return if (address.startsWith("naddr") || address.startsWith("nostr:naddr")) {
parseNAddr(address)
else
} else {
parseAtag(address, relay)
}
}
fun parseAtag(atag: String, relay: String?): ATag? {
return try {
@@ -51,7 +53,7 @@ data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String, val rela
Hex.decode(parts[1])
ATag(parts[0].toInt(), parts[1], parts[2], relay)
} catch (t: Throwable) {
Log.w("ATag", "Error parsing A Tag: ${atag}: ${t.message}")
Log.w("ATag", "Error parsing A Tag: $atag: ${t.message}")
null
}
}
@@ -67,12 +69,12 @@ data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String, val rela
val author = tlv.get(NIP19TLVTypes.AUTHOR.id)?.get(0)?.toHexKey()
val kind = tlv.get(NIP19TLVTypes.KIND.id)?.get(0)?.let { toInt32(it) }
if (kind != null && author != null)
if (kind != null && author != null) {
return ATag(kind, author, d, relay)
}
}
} catch (e: Throwable) {
Log.w( "ATag", "Issue trying to Decode NIP19 ${this}: ${e.message}")
Log.w("ATag", "Issue trying to Decode NIP19 $this: ${e.message}")
// e.printStackTrace()
}

View File

@@ -2,9 +2,8 @@ package com.vitorpamplona.amethyst.service.model
import com.vitorpamplona.amethyst.model.HexKey
import com.vitorpamplona.amethyst.model.toHexKey
import java.util.Date
import nostr.postr.Utils
import nostr.postr.toHex
import java.util.Date
class LnZapRequestEvent(
id: HexKey,

View File

@@ -2,9 +2,8 @@ package com.vitorpamplona.amethyst.service.model
import com.vitorpamplona.amethyst.model.HexKey
import com.vitorpamplona.amethyst.model.toHexKey
import java.util.Date
import nostr.postr.Utils
import nostr.postr.toHex
import java.util.Date
class ReactionEvent(
id: HexKey,

View File

@@ -2,9 +2,8 @@ package com.vitorpamplona.amethyst.service.model
import com.vitorpamplona.amethyst.model.HexKey
import com.vitorpamplona.amethyst.model.toHexKey
import java.util.Date
import nostr.postr.Utils
import nostr.postr.toHex
import java.util.Date
data class ReportedKey(val key: String, val reportType: ReportEvent.ReportType)
@@ -95,6 +94,6 @@ class ReportEvent (
SPAM,
IMPERSONATION,
NUDITY,
PROFANITY,
PROFANITY
}
}

View File

@@ -3,8 +3,8 @@ package com.vitorpamplona.amethyst.service.model
import com.vitorpamplona.amethyst.model.HexKey
import com.vitorpamplona.amethyst.model.toHexKey
import com.vitorpamplona.amethyst.service.relays.Client
import java.util.Date
import nostr.postr.Utils
import java.util.Date
class RepostEvent(
id: HexKey,
@@ -15,7 +15,6 @@ class RepostEvent (
sig: HexKey
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
fun boostedPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
fun originalAuthor() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
fun taggedAddresses() = tags.filter { it.firstOrNull() == "a" }.mapNotNull {

View File

@@ -28,7 +28,7 @@ fun ClickableRoute(
val text = user.toBestDisplayName()
ClickableText(
text = AnnotatedString("@${text} "),
text = AnnotatedString("@$text "),
onClick = { navController.navigate(route) },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
)

View File

@@ -8,28 +8,20 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.LocalContentColor
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.text.Paragraph
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextDirection
import androidx.compose.ui.unit.dp
@@ -41,9 +33,9 @@ import com.halilibo.richtext.markdown.MarkdownParseOptions
import com.halilibo.richtext.ui.RichTextStyle
import com.halilibo.richtext.ui.material.MaterialRichText
import com.halilibo.richtext.ui.resolveDefaults
import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.service.Nip19
import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil
import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import java.net.MalformedURLException
@@ -79,9 +71,8 @@ fun RichTextViewer(
tags: List<List<String>>?,
backgroundColor: Color,
accountViewModel: AccountViewModel,
navController: NavController,
navController: NavController
) {
val myMarkDownStyle = RichTextStyle().resolveDefaults().copy(
codeBlockStyle = RichTextStyle().resolveDefaults().codeBlockStyle?.copy(
textStyle = TextStyle(
@@ -113,27 +104,25 @@ fun RichTextViewer(
)
Column(modifier = modifier.animateContentSize()) {
if ( content.startsWith("# ")
|| content.contains("##")
|| content.contains("**")
|| content.contains("__")
|| content.contains("```")
if (content.startsWith("# ") ||
content.contains("##") ||
content.contains("**") ||
content.contains("__") ||
content.contains("```")
) {
MaterialRichText(
style = myMarkDownStyle,
style = myMarkDownStyle
) {
Markdown(
content = content,
markdownParseOptions = MarkdownParseOptions.Default,
markdownParseOptions = MarkdownParseOptions.Default
)
}
} else {
// FlowRow doesn't work well with paragraphs. So we need to split them
content.split('\n').forEach { paragraph ->
FlowRow() {
val s = if (isArabic(paragraph)) paragraph.split(' ').reversed() else paragraph.split(' ');
val s = if (isArabic(paragraph)) paragraph.split(' ').reversed() else paragraph.split(' ')
s.forEach { word: String ->
if (canPreview) {
// Explicit URL
@@ -162,7 +151,7 @@ fun RichTextViewer(
} else {
Text(
text = "$word ",
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
)
}
} else {
@@ -181,7 +170,7 @@ fun RichTextViewer(
} else {
Text(
text = "$word ",
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
)
}
}
@@ -196,19 +185,18 @@ private fun isArabic(text: String): Boolean {
return text.any { it in '\u0600'..'\u06FF' || it in '\u0750'..'\u077F' }
}
fun isBechLink(word: String): Boolean {
return word.startsWith("nostr:", true)
|| word.startsWith("npub1", true)
|| word.startsWith("naddr1", true)
|| word.startsWith("note1", true)
|| word.startsWith("nprofile1", true)
|| word.startsWith("nevent1", true)
|| word.startsWith("@npub1", true)
|| word.startsWith("@note1", true)
|| word.startsWith("@addr1", true)
|| word.startsWith("@nprofile1", true)
|| word.startsWith("@nevent1", true)
return word.startsWith("nostr:", true) ||
word.startsWith("npub1", true) ||
word.startsWith("naddr1", true) ||
word.startsWith("note1", true) ||
word.startsWith("nprofile1", true) ||
word.startsWith("nevent1", true) ||
word.startsWith("@npub1", true) ||
word.startsWith("@note1", true) ||
word.startsWith("@addr1", true) ||
word.startsWith("@nprofile1", true) ||
word.startsWith("@nevent1", true)
}
@Composable
@@ -218,7 +206,7 @@ fun BechLink(word: String, navController: NavController) {
} else if (word.startsWith("@")) {
word.replaceFirst("@", "nostr:")
} else {
"nostr:${word}"
"nostr:$word"
}
val nip19Route = try {
@@ -234,7 +222,6 @@ fun BechLink(word: String, navController: NavController) {
}
}
@Composable
fun TagLink(word: String, tags: List<List<String>>, canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, navController: NavController) {
val matcher = tagIndex.matcher(word)
@@ -243,7 +230,7 @@ fun TagLink(word: String, tags: List<List<String>>, canPreview: Boolean, backgro
matcher.find()
matcher.group(1).toInt()
} catch (e: Exception) {
println("Couldn't link tag ${word}")
println("Couldn't link tag $word")
null
}
@@ -294,7 +281,8 @@ fun TagLink(word: String, tags: List<List<String>>, canPreview: Boolean, backgro
// if here the tag is not a valid Nostr Hex
Text(text = "$word ")
}
} else
} else {
Text(text = "$word ")
}
}
}