diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/nip19/Nip19.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/nip19/Nip19.kt index 796936331..b8f84d8bc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/nip19/Nip19.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/nip19/Nip19.kt @@ -1,35 +1,47 @@ package com.vitorpamplona.amethyst.service.nip19 +import android.util.Log import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.bechToBytes +import java.util.regex.Pattern object Nip19 { enum class Type { USER, NOTE, RELAY, ADDRESS } - data class Return(val type: Type, val hex: String, val relay: String? = null) + val nip19regex = Pattern.compile("(nostr:)?@?(nsec1|npub1|nevent1|naddr1|note1|nprofile1|nrelay1)([qpzry9x8gf2tvdw0s3jn54khce6mua7l]+)(.*)", Pattern.CASE_INSENSITIVE) + + data class Return(val type: Type, val hex: String, val relay: String? = null, val additionalChars: String = "") fun uriToRoute(uri: String?): Return? { - try { - val key = uri?.removePrefix("nostr:") ?: return null + if (uri == null) return null - val bytes = key.bechToBytes() - if (key.startsWith("npub")) { - return npub(bytes) - } else if (key.startsWith("note")) { - return note(bytes) - } else if (key.startsWith("nprofile")) { - return nprofile(bytes) - } else if (key.startsWith("nevent")) { - return nevent(bytes) - } else if (key.startsWith("nrelay")) { - return nrelay(bytes) - } else if (key.startsWith("naddr")) { - return naddr(bytes) + try { + println("Issue trying to Decode NIP19 $uri") + + val matcher = nip19regex.matcher(uri) + matcher.find() + val uriScheme = matcher.group(1) // nostr: + val type = matcher.group(2) // npub1 + val key = matcher.group(3) // bech32 + val additionalChars = matcher.group(4) ?: "" // additional chars + + println("Issue trying to Decode NIP19 Additional Chars $additionalChars") + + val bytes = (type + key).bechToBytes() + val parsed = when (type.lowercase()) { + "npub1" -> npub(bytes) + "note1" -> note(bytes) + "nprofile1" -> nprofile(bytes) + "nevent1" -> nevent(bytes) + "nrelay1" -> nrelay(bytes) + "naddr1" -> naddr(bytes) + else -> null } + return parsed?.copy(additionalChars = additionalChars) } catch (e: Throwable) { - println("Issue trying to Decode NIP19 $uri: ${e.message}") + Log.e("NIP19 Parser", "Issue trying to Decode NIP19 $uri: ${e.message}", e) } return null diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt index af4cc78c7..0e02d1e9f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt @@ -7,7 +7,8 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.withStyle import androidx.navigation.NavController import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent @@ -24,58 +25,55 @@ fun ClickableRoute( val userState by userBase.live().metadata.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) - ) + CreateClickableText(user.toBestDisplayName(), nip19.additionalChars, "User/${nip19.hex}", navController) } else if (nip19.type == Nip19.Type.ADDRESS) { val noteBase = LocalCache.checkGetOrCreateAddressableNote(nip19.hex) if (noteBase == null) { Text( - "@${nip19.hex} " + "@${nip19.hex}${nip19.additionalChars} " ) } else { val noteState by noteBase.live().metadata.observeAsState() val note = noteState?.note ?: return - ClickableText( - text = AnnotatedString("@${note.idDisplayNote()} "), - onClick = { navController.navigate("Note/${nip19.hex}") }, - style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary) - ) + CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Note/${nip19.hex}", navController) } } else if (nip19.type == Nip19.Type.NOTE) { val noteBase = LocalCache.getOrCreateNote(nip19.hex) val noteState by noteBase.live().metadata.observeAsState() val note = noteState?.note ?: return + val channel = note.channel() 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()?.toBestDisplayName()} "), - onClick = { navController.navigate("Channel/${note.channel()?.idHex}") }, - style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary) - ) + CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Channel/${nip19.hex}", navController) + } else if (channel != null) { + CreateClickableText(channel.toBestDisplayName(), nip19.additionalChars, "Channel/${note.channel()?.idHex}", navController) } else { - ClickableText( - text = AnnotatedString("@${note.idDisplayNote()} "), - onClick = { navController.navigate("Note/${nip19.hex}") }, - style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary) - ) + CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Note/${nip19.hex}", navController) } } else { Text( - "@${nip19.hex} " + "@${nip19.hex}${nip19.additionalChars} " ) } } + +@Composable +fun CreateClickableText(clickablePart: String, suffix: String, route: String, navController: NavController) { + ClickableText( + text = buildAnnotatedString { + withStyle( + LocalTextStyle.current.copy(color = MaterialTheme.colors.primary).toSpanStyle() + ) { + append("@$clickablePart") + } + withStyle( + LocalTextStyle.current.copy(color = MaterialTheme.colors.onBackground).toSpanStyle() + ) { + append("$suffix ") + } + }, + onClick = { navController.navigate(route) } + ) +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index 29681a8a6..6c390d583 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -43,8 +43,8 @@ import java.net.URISyntaxException import java.net.URL import java.util.regex.Pattern -val imageExtension = Pattern.compile("(.*/)*.+\\.(png|jpg|gif|bmp|jpeg|webp|svg)$") -val videoExtension = Pattern.compile("(.*/)*.+\\.(mp4|avi|wmv|mpg|amv|webm|mov)$") +val imageExtension = Pattern.compile("(.*/)*.+\\.(png|jpg|gif|bmp|jpeg|webp|svg)$", Pattern.CASE_INSENSITIVE) +val videoExtension = Pattern.compile("(.*/)*.+\\.(mp4|avi|wmv|mpg|amv|webm|mov)$", Pattern.CASE_INSENSITIVE) val noProtocolUrlValidator = Pattern.compile("^[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&//=]*)$") val tagIndex = Pattern.compile(".*\\#\\[([0-9]+)\\].*") @@ -197,19 +197,7 @@ fun isBechLink(word: String): Boolean { @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 - } + val nip19Route = Nip19.uriToRoute(word) if (nip19Route == null) { Text(text = "$word ")