Creating linkable routes for NIP19 in content.

This commit is contained in:
Vitor Pamplona
2023-01-30 13:50:24 -03:00
parent fb8d6daf3d
commit a0ba313661
5 changed files with 111 additions and 4 deletions

View File

@@ -22,7 +22,7 @@ class Note(val idHex: String) {
// These fields are always available.
// They are immutable
val id = Hex.decode(idHex)
val idDisplayHex = id.toShortenHex()
val idDisplayNote = id.toNote().toShortenHex()
// These fields are only available after the Text Note event is received.
// They are immutable after that.

View File

@@ -15,10 +15,11 @@ import kotlinx.coroutines.launch
import nostr.postr.events.ContactListEvent
import nostr.postr.events.Event
import nostr.postr.events.MetadataEvent
import nostr.postr.toNpub
class User(val pubkey: ByteArray) {
val pubkeyHex = pubkey.toHexKey()
val pubkeyDisplayHex = pubkey.toShortenHex()
val pubkeyDisplayHex = pubkey.toNpub().toShortenHex()
var info = UserMetadata()

View File

@@ -36,6 +36,7 @@ class Nip19 {
}
}
} catch (e: Throwable) {
println("Trying to Decode NIP19: ${uri}")
e.printStackTrace()
}

View File

@@ -0,0 +1,63 @@
package com.vitorpamplona.amethyst.ui.components
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.text.AnnotatedString
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.toByteArray
import com.vitorpamplona.amethyst.model.toNote
import com.vitorpamplona.amethyst.service.Nip19
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
import com.vitorpamplona.amethyst.ui.note.toShortenHex
import nostr.postr.toNpub
@Composable
fun ClickableRoute(
nip19: Nip19.Return,
navController: NavController
) {
if (nip19.type == Nip19.Type.USER) {
val userBase = LocalCache.getOrCreateUser(nip19.hex.toByteArray())
val userState by userBase.live.observeAsState()
val user = userState?.user ?: return
val route = "User/${nip19.hex}"
val text = user.toBestDisplayName()
ClickableText(
text = AnnotatedString("@${text} "),
onClick = { navController.navigate(route) },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
)
} else {
val noteBase = LocalCache.getOrCreateNote(nip19.hex)
val noteState by noteBase.live.observeAsState()
val note = noteState?.note ?: return
if (note.event is ChannelCreateEvent) {
ClickableText(
text = AnnotatedString("@${note.idDisplayNote} "),
onClick = { navController.navigate("Channel/${nip19.hex}") },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
)
} else if (note.channel != null) {
ClickableText(
text = AnnotatedString("@${note.channel} "),
onClick = { navController.navigate("Channel/${nip19.hex}") },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
)
} else {
ClickableText(
text = AnnotatedString("@${note.idDisplayNote} "),
onClick = { navController.navigate("Note/${nip19.hex}") },
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary)
)
}
}
}

View File

@@ -17,12 +17,16 @@ import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.lnurl.LnInvoiceUtil
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.toByteArray
import com.vitorpamplona.amethyst.model.toNote
import com.vitorpamplona.amethyst.service.Nip19
import com.vitorpamplona.amethyst.ui.note.toShortenHex
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import java.net.MalformedURLException
import java.net.URISyntaxException
import java.net.URL
import java.util.regex.Pattern
import nostr.postr.toNpub
val imageExtension = Pattern.compile("(.*/)*.+\\.(png|jpg|gif|bmp|jpeg|webp|svg)$")
val videoExtension = Pattern.compile("(.*/)*.+\\.(mp4|avi|wmv|mpg|amv|webm)$")
@@ -74,6 +78,8 @@ fun RichTextViewer(content: String, tags: List<List<String>>?, navController: Na
UrlPreview("https://$word", word)
} else if (tagIndex.matcher(word).matches() && tags != null) {
TagLink(word, tags, navController)
} else if (isBechLink(word)) {
BechLink(word, navController)
} else {
Text(
text = "$word ",
@@ -87,6 +93,42 @@ fun RichTextViewer(content: String, tags: List<List<String>>?, navController: Na
}
}
fun isBechLink(word: String): Boolean {
return word.startsWith("nostr:", true)
|| word.startsWith("npub1", true)
|| word.startsWith("note1", true)
|| word.startsWith("nprofile1", true)
|| word.startsWith("nevent1", true)
|| word.startsWith("@npub1", true)
|| word.startsWith("@note1", true)
|| word.startsWith("@nprofile1", true)
|| word.startsWith("@nevent1", true)
}
@Composable
fun BechLink(word: String, navController: NavController) {
val uri = if (word.startsWith("nostr", true)) {
word
} else if (word.startsWith("@")) {
word.replaceFirst("@", "nostr:")
} else {
"nostr:${word}"
}
val nip19Route = try {
Nip19().uriToRoute(uri)
} catch (e: Exception) {
null
}
if (nip19Route == null) {
Text(text = "$word ")
} else {
ClickableRoute(nip19Route, navController)
}
}
@Composable
fun TagLink(word: String, tags: List<List<String>>, navController: NavController) {
val matcher = tagIndex.matcher(word)
@@ -109,14 +151,14 @@ fun TagLink(word: String, tags: List<List<String>>, navController: NavController
if (user != null) {
ClickableUserTag(user, navController)
} else {
Text(text = "${tags[index][1].toShortenHex()} ")
Text(text = "${tags[index][1].toByteArray().toNpub().toShortenHex()} ")
}
} else if (tags[index][0] == "e") {
val note = LocalCache.notes[tags[index][1]]
if (note != null) {
ClickableNoteTag(note, navController)
} else {
Text(text = "${tags[index][1].toShortenHex()} ")
Text(text = "${tags[index][1].toByteArray().toNote().toShortenHex()} ")
}
} else
Text(text = "$word ")