- Adds hashtags and #[n] rendering to markdown
- Shortcuts the link to Amethyst's router.
This commit is contained in:
@@ -46,35 +46,7 @@ class MainActivity : FragmentActivity() {
|
|||||||
|
|
||||||
val uri = intent?.data?.toString()
|
val uri = intent?.data?.toString()
|
||||||
|
|
||||||
val startingPage = if (uri.equals("nostr:Notifications", true)) {
|
val startingPage = uriToRoute(uri)
|
||||||
Route.Notification.route.replace("{scrollToTop}", "true")
|
|
||||||
} else {
|
|
||||||
val nip19 = Nip19.uriToRoute(uri)
|
|
||||||
when (nip19?.type) {
|
|
||||||
Nip19.Type.USER -> "User/${nip19.hex}"
|
|
||||||
Nip19.Type.NOTE -> "Note/${nip19.hex}"
|
|
||||||
Nip19.Type.EVENT -> {
|
|
||||||
if (nip19.kind == PrivateDmEvent.kind) {
|
|
||||||
"Room/${nip19.author}"
|
|
||||||
} else if (nip19.kind == ChannelMessageEvent.kind || nip19.kind == ChannelCreateEvent.kind || nip19.kind == ChannelMetadataEvent.kind) {
|
|
||||||
"Channel/${nip19.hex}"
|
|
||||||
} else {
|
|
||||||
"Event/${nip19.hex}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Nip19.Type.ADDRESS -> "Note/${nip19.hex}"
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
} ?: try {
|
|
||||||
uri?.let {
|
|
||||||
Nip47.parse(it)
|
|
||||||
val encodedUri = URLEncoder.encode(it, StandardCharsets.UTF_8.toString())
|
|
||||||
Route.Home.base + "?nip47=" + encodedUri
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initializes video cache.
|
// Initializes video cache.
|
||||||
VideoCache.init(this.applicationContext)
|
VideoCache.init(this.applicationContext)
|
||||||
@@ -150,3 +122,39 @@ class GetMediaActivityResultContract : ActivityResultContracts.GetContent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun uriToRoute(uri: String?): String? {
|
||||||
|
return if (uri.equals("nostr:Notifications", true)) {
|
||||||
|
Route.Notification.route.replace("{scrollToTop}", "true")
|
||||||
|
} else {
|
||||||
|
if (uri?.startsWith("nostr:Hashtag?id=") == true) {
|
||||||
|
Route.Hashtag.route.replace("{id}", uri.removePrefix("nostr:Hashtag?id="))
|
||||||
|
} else {
|
||||||
|
val nip19 = Nip19.uriToRoute(uri)
|
||||||
|
when (nip19?.type) {
|
||||||
|
Nip19.Type.USER -> "User/${nip19.hex}"
|
||||||
|
Nip19.Type.NOTE -> "Note/${nip19.hex}"
|
||||||
|
Nip19.Type.EVENT -> {
|
||||||
|
if (nip19.kind == PrivateDmEvent.kind) {
|
||||||
|
"Room/${nip19.author}"
|
||||||
|
} else if (nip19.kind == ChannelMessageEvent.kind || nip19.kind == ChannelCreateEvent.kind || nip19.kind == ChannelMetadataEvent.kind) {
|
||||||
|
"Channel/${nip19.hex}"
|
||||||
|
} else {
|
||||||
|
"Event/${nip19.hex}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Nip19.Type.ADDRESS -> "Note/${nip19.hex}"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
} ?: try {
|
||||||
|
uri?.let {
|
||||||
|
Nip47.parse(it)
|
||||||
|
val encodedUri = URLEncoder.encode(it, StandardCharsets.UTF_8.toString())
|
||||||
|
Route.Home.base + "?nip47=" + encodedUri
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.compositeOver
|
import androidx.compose.ui.graphics.compositeOver
|
||||||
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.*
|
import androidx.compose.ui.text.*
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
|
||||||
import androidx.compose.ui.text.style.TextDirection
|
import androidx.compose.ui.text.style.TextDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@@ -43,6 +43,7 @@ import com.vitorpamplona.amethyst.service.lnurl.LnWithdrawalUtil
|
|||||||
import com.vitorpamplona.amethyst.service.nip19.Nip19
|
import com.vitorpamplona.amethyst.service.nip19.Nip19
|
||||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.uriToRoute
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -99,7 +100,7 @@ fun RichTextViewer(
|
|||||||
|
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
if (isMarkdown) {
|
if (isMarkdown) {
|
||||||
RenderContentAsMarkdown(content, backgroundColor)
|
RenderContentAsMarkdown(content, backgroundColor, tags, navController)
|
||||||
} else {
|
} else {
|
||||||
RenderRegular(content, tags, canPreview, backgroundColor, accountViewModel, navController)
|
RenderRegular(content, tags, canPreview, backgroundColor, accountViewModel, navController)
|
||||||
}
|
}
|
||||||
@@ -310,7 +311,7 @@ fun RenderCustomEmoji(word: String, customEmoji: Map<String, String>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RenderContentAsMarkdown(content: String, backgroundColor: Color) {
|
private fun RenderContentAsMarkdown(content: String, backgroundColor: Color, tags: List<List<String>>?, navController: NavController) {
|
||||||
val myMarkDownStyle = richTextDefaults.copy(
|
val myMarkDownStyle = richTextDefaults.copy(
|
||||||
codeBlockStyle = richTextDefaults.codeBlockStyle?.copy(
|
codeBlockStyle = richTextDefaults.codeBlockStyle?.copy(
|
||||||
textStyle = TextStyle(
|
textStyle = TextStyle(
|
||||||
@@ -334,7 +335,6 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: Color) {
|
|||||||
),
|
),
|
||||||
stringStyle = richTextDefaults.stringStyle?.copy(
|
stringStyle = richTextDefaults.stringStyle?.copy(
|
||||||
linkStyle = SpanStyle(
|
linkStyle = SpanStyle(
|
||||||
textDecoration = TextDecoration.Underline,
|
|
||||||
color = MaterialTheme.colors.primary
|
color = MaterialTheme.colors.primary
|
||||||
),
|
),
|
||||||
codeStyle = SpanStyle(
|
codeStyle = SpanStyle(
|
||||||
@@ -352,14 +352,14 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: Color) {
|
|||||||
|
|
||||||
LaunchedEffect(key1 = content) {
|
LaunchedEffect(key1 = content) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
nip19References = returnNIP19References(content)
|
nip19References = returnNIP19References(content, tags)
|
||||||
markdownWithSpecialContent = returnMarkdownWithSpecialContent(content)
|
markdownWithSpecialContent = returnMarkdownWithSpecialContent(content, tags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = refresh) {
|
LaunchedEffect(key1 = refresh) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val newMarkdownWithSpecialContent = returnMarkdownWithSpecialContent(content)
|
val newMarkdownWithSpecialContent = returnMarkdownWithSpecialContent(content, tags)
|
||||||
if (markdownWithSpecialContent != newMarkdownWithSpecialContent) {
|
if (markdownWithSpecialContent != newMarkdownWithSpecialContent) {
|
||||||
markdownWithSpecialContent = newMarkdownWithSpecialContent
|
markdownWithSpecialContent = newMarkdownWithSpecialContent
|
||||||
}
|
}
|
||||||
@@ -389,17 +389,23 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: Color) {
|
|||||||
baseNote?.let {
|
baseNote?.let {
|
||||||
val noteState by it.live().metadata.observeAsState()
|
val noteState by it.live().metadata.observeAsState()
|
||||||
if (noteState?.note?.event != null) {
|
if (noteState?.note?.event != null) {
|
||||||
refresh++
|
LaunchedEffect(key1 = noteState) {
|
||||||
|
refresh++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
baseUser?.let {
|
baseUser?.let {
|
||||||
val userState by it.live().metadata.observeAsState()
|
val userState by it.live().metadata.observeAsState()
|
||||||
if (userState?.user?.info != null) {
|
if (userState?.user?.info != null) {
|
||||||
refresh++
|
LaunchedEffect(key1 = userState) {
|
||||||
|
refresh++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val uri = LocalUriHandler.current
|
||||||
|
|
||||||
markdownWithSpecialContent?.let {
|
markdownWithSpecialContent?.let {
|
||||||
MaterialRichText(
|
MaterialRichText(
|
||||||
style = myMarkDownStyle
|
style = myMarkDownStyle
|
||||||
@@ -407,26 +413,70 @@ private fun RenderContentAsMarkdown(content: String, backgroundColor: Color) {
|
|||||||
Markdown(
|
Markdown(
|
||||||
content = it,
|
content = it,
|
||||||
markdownParseOptions = MarkdownParseOptions.Default
|
markdownParseOptions = MarkdownParseOptions.Default
|
||||||
)
|
) { link ->
|
||||||
|
val route = uriToRoute(link)
|
||||||
|
if (route != null) {
|
||||||
|
navController.navigate(route)
|
||||||
|
} else {
|
||||||
|
runCatching { uri.openUri(link) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDisplayNameFromNip19(nip19: Nip19.Return): String? {
|
private fun getDisplayNameAndNIP19FromTag(tag: String, tags: List<List<String>>): Pair<String, String>? {
|
||||||
if (nip19.type == Nip19.Type.USER) {
|
val matcher = tagIndex.matcher(tag)
|
||||||
return LocalCache.users[nip19.hex]?.toBestDisplayName()
|
val (index, suffix) = try {
|
||||||
} else if (nip19.type == Nip19.Type.NOTE) {
|
matcher.find()
|
||||||
return LocalCache.notes[nip19.hex]?.idDisplayNote()
|
Pair(matcher.group(1)?.toInt(), matcher.group(2) ?: "")
|
||||||
} else if (nip19.type == Nip19.Type.ADDRESS) {
|
} catch (e: Exception) {
|
||||||
return LocalCache.addressables[nip19.hex]?.idDisplayNote()
|
Log.w("Tag Parser", "Couldn't link tag $tag", e)
|
||||||
} else if (nip19.type == Nip19.Type.EVENT) {
|
Pair(null, null)
|
||||||
return LocalCache.notes[nip19.hex]?.idDisplayNote() ?: LocalCache.addressables[nip19.hex]?.idDisplayNote()
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (index != null && index >= 0 && index < tags.size) {
|
||||||
|
val tag = tags[index]
|
||||||
|
|
||||||
|
if (tag.size > 1) {
|
||||||
|
if (tag[0] == "p") {
|
||||||
|
LocalCache.checkGetOrCreateUser(tag[1])?.let {
|
||||||
|
return Pair(it.toBestDisplayName(), it.pubkeyNpub())
|
||||||
|
}
|
||||||
|
} else if (tag[0] == "e" || tag[0] == "a") {
|
||||||
|
LocalCache.checkGetOrCreateNote(tag[1])?.let {
|
||||||
|
return Pair(it.idDisplayNote(), it.toNEvent())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun returnNIP19References(content: String): List<Nip19.Return> {
|
private fun getDisplayNameFromNip19(nip19: Nip19.Return): Pair<String, String>? {
|
||||||
|
if (nip19.type == Nip19.Type.USER) {
|
||||||
|
LocalCache.users[nip19.hex]?.let {
|
||||||
|
return Pair(it.toBestDisplayName(), it.pubkeyNpub())
|
||||||
|
}
|
||||||
|
} else if (nip19.type == Nip19.Type.NOTE) {
|
||||||
|
LocalCache.notes[nip19.hex]?.let {
|
||||||
|
return Pair(it.idDisplayNote(), it.toNEvent())
|
||||||
|
}
|
||||||
|
} else if (nip19.type == Nip19.Type.ADDRESS) {
|
||||||
|
LocalCache.addressables[nip19.hex]?.let {
|
||||||
|
return Pair(it.idDisplayNote(), it.toNEvent())
|
||||||
|
}
|
||||||
|
} else if (nip19.type == Nip19.Type.EVENT) {
|
||||||
|
LocalCache.notes[nip19.hex]?.let {
|
||||||
|
return Pair(it.idDisplayNote(), it.toNEvent())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun returnNIP19References(content: String, tags: List<List<String>>?): List<Nip19.Return> {
|
||||||
val listOfReferences = mutableListOf<Nip19.Return>()
|
val listOfReferences = mutableListOf<Nip19.Return>()
|
||||||
content.split('\n').forEach { paragraph ->
|
content.split('\n').forEach { paragraph ->
|
||||||
paragraph.split(' ').forEach { word: String ->
|
paragraph.split(' ').forEach { word: String ->
|
||||||
@@ -438,10 +488,21 @@ private fun returnNIP19References(content: String): List<Nip19.Return> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tags?.forEach {
|
||||||
|
if (it[0] == "p" && it.size > 1) {
|
||||||
|
listOfReferences.add(Nip19.Return(Nip19.Type.USER, it[1], null, null, null, ""))
|
||||||
|
} else if (it[0] == "e" && it.size > 1) {
|
||||||
|
listOfReferences.add(Nip19.Return(Nip19.Type.NOTE, it[1], null, null, null, ""))
|
||||||
|
} else if (it[0] == "a" && it.size > 1) {
|
||||||
|
listOfReferences.add(Nip19.Return(Nip19.Type.ADDRESS, it[1], null, null, null, ""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return listOfReferences
|
return listOfReferences
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun returnMarkdownWithSpecialContent(content: String): String {
|
private fun returnMarkdownWithSpecialContent(content: String, tags: List<List<String>>?): String {
|
||||||
var returnContent = ""
|
var returnContent = ""
|
||||||
content.split('\n').forEach { paragraph ->
|
content.split('\n').forEach { paragraph ->
|
||||||
paragraph.split(' ').forEach { word: String ->
|
paragraph.split(' ').forEach { word: String ->
|
||||||
@@ -454,15 +515,43 @@ private fun returnMarkdownWithSpecialContent(content: String): String {
|
|||||||
} else if (isBechLink(word)) {
|
} else if (isBechLink(word)) {
|
||||||
val parsedNip19 = Nip19.uriToRoute(word)
|
val parsedNip19 = Nip19.uriToRoute(word)
|
||||||
returnContent += if (parsedNip19 !== null) {
|
returnContent += if (parsedNip19 !== null) {
|
||||||
val displayName = getDisplayNameFromNip19(parsedNip19)
|
val pair = getDisplayNameFromNip19(parsedNip19)
|
||||||
if (displayName != null) {
|
if (pair != null) {
|
||||||
"[@$displayName](nostr://$word) "
|
val (displayName, nip19) = pair
|
||||||
|
"[$displayName](nostr:$nip19) "
|
||||||
} else {
|
} else {
|
||||||
"$word "
|
"$word "
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
"$word "
|
"$word "
|
||||||
}
|
}
|
||||||
|
} else if (word.startsWith("#")) {
|
||||||
|
if (tagIndex.matcher(word).matches() && tags != null) {
|
||||||
|
val pair = getDisplayNameAndNIP19FromTag(word, tags)
|
||||||
|
if (pair != null) {
|
||||||
|
returnContent += "[${pair.first}](nostr:${pair.second}) "
|
||||||
|
} else {
|
||||||
|
returnContent += "$word "
|
||||||
|
}
|
||||||
|
} else if (hashTagsPattern.matcher(word).matches()) {
|
||||||
|
val hashtagMatcher = hashTagsPattern.matcher(word)
|
||||||
|
|
||||||
|
val (myTag, mySuffix) = try {
|
||||||
|
hashtagMatcher.find()
|
||||||
|
Pair(hashtagMatcher.group(1), hashtagMatcher.group(2))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("Hashtag Parser", "Couldn't link hashtag $word", e)
|
||||||
|
Pair(null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (myTag != null) {
|
||||||
|
returnContent += "[#$myTag](nostr:Hashtag?id=$myTag)$mySuffix "
|
||||||
|
} else {
|
||||||
|
returnContent += "$word "
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
returnContent += "$word "
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
returnContent += "$word "
|
returnContent += "$word "
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user