mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-26 18:56:27 +02:00
Creating linkable routes for NIP19 in content.
This commit is contained in:
@@ -22,7 +22,7 @@ class Note(val idHex: String) {
|
|||||||
// These fields are always available.
|
// These fields are always available.
|
||||||
// They are immutable
|
// They are immutable
|
||||||
val id = Hex.decode(idHex)
|
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.
|
// These fields are only available after the Text Note event is received.
|
||||||
// They are immutable after that.
|
// They are immutable after that.
|
||||||
|
@@ -15,10 +15,11 @@ import kotlinx.coroutines.launch
|
|||||||
import nostr.postr.events.ContactListEvent
|
import nostr.postr.events.ContactListEvent
|
||||||
import nostr.postr.events.Event
|
import nostr.postr.events.Event
|
||||||
import nostr.postr.events.MetadataEvent
|
import nostr.postr.events.MetadataEvent
|
||||||
|
import nostr.postr.toNpub
|
||||||
|
|
||||||
class User(val pubkey: ByteArray) {
|
class User(val pubkey: ByteArray) {
|
||||||
val pubkeyHex = pubkey.toHexKey()
|
val pubkeyHex = pubkey.toHexKey()
|
||||||
val pubkeyDisplayHex = pubkey.toShortenHex()
|
val pubkeyDisplayHex = pubkey.toNpub().toShortenHex()
|
||||||
|
|
||||||
var info = UserMetadata()
|
var info = UserMetadata()
|
||||||
|
|
||||||
|
@@ -36,6 +36,7 @@ class Nip19 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
|
println("Trying to Decode NIP19: ${uri}")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -17,12 +17,16 @@ import com.google.accompanist.flowlayout.FlowRow
|
|||||||
import com.vitorpamplona.amethyst.lnurl.LnInvoiceUtil
|
import com.vitorpamplona.amethyst.lnurl.LnInvoiceUtil
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
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.note.toShortenHex
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import java.net.MalformedURLException
|
import java.net.MalformedURLException
|
||||||
import java.net.URISyntaxException
|
import java.net.URISyntaxException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
import nostr.postr.toNpub
|
||||||
|
|
||||||
val imageExtension = Pattern.compile("(.*/)*.+\\.(png|jpg|gif|bmp|jpeg|webp|svg)$")
|
val imageExtension = Pattern.compile("(.*/)*.+\\.(png|jpg|gif|bmp|jpeg|webp|svg)$")
|
||||||
val videoExtension = Pattern.compile("(.*/)*.+\\.(mp4|avi|wmv|mpg|amv|webm)$")
|
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)
|
UrlPreview("https://$word", word)
|
||||||
} else if (tagIndex.matcher(word).matches() && tags != null) {
|
} else if (tagIndex.matcher(word).matches() && tags != null) {
|
||||||
TagLink(word, tags, navController)
|
TagLink(word, tags, navController)
|
||||||
|
} else if (isBechLink(word)) {
|
||||||
|
BechLink(word, navController)
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = "$word ",
|
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
|
@Composable
|
||||||
fun TagLink(word: String, tags: List<List<String>>, navController: NavController) {
|
fun TagLink(word: String, tags: List<List<String>>, navController: NavController) {
|
||||||
val matcher = tagIndex.matcher(word)
|
val matcher = tagIndex.matcher(word)
|
||||||
@@ -109,14 +151,14 @@ fun TagLink(word: String, tags: List<List<String>>, navController: NavController
|
|||||||
if (user != null) {
|
if (user != null) {
|
||||||
ClickableUserTag(user, navController)
|
ClickableUserTag(user, navController)
|
||||||
} else {
|
} else {
|
||||||
Text(text = "${tags[index][1].toShortenHex()} ")
|
Text(text = "${tags[index][1].toByteArray().toNpub().toShortenHex()} ")
|
||||||
}
|
}
|
||||||
} else if (tags[index][0] == "e") {
|
} else if (tags[index][0] == "e") {
|
||||||
val note = LocalCache.notes[tags[index][1]]
|
val note = LocalCache.notes[tags[index][1]]
|
||||||
if (note != null) {
|
if (note != null) {
|
||||||
ClickableNoteTag(note, navController)
|
ClickableNoteTag(note, navController)
|
||||||
} else {
|
} else {
|
||||||
Text(text = "${tags[index][1].toShortenHex()} ")
|
Text(text = "${tags[index][1].toByteArray().toNote().toShortenHex()} ")
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
Text(text = "$word ")
|
Text(text = "$word ")
|
||||||
|
Reference in New Issue
Block a user