From 83f1e523eaba3a8c06a511ea3cf2506a6420c37a Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 21 Aug 2024 17:05:14 -0400 Subject: [PATCH] Creates links to njump when events can't be found on Amethyst --- .../amethyst/model/AntiSpamFilter.kt | 3 +- .../amethyst/ui/components/ClickableRoute.kt | 99 +++++++++++++------ .../markdown/MarkdownMediaRenderer.kt | 7 +- .../amethyst/ui/note/NoteQuickActionMenu.kt | 14 ++- .../amethyst/ui/note/types/Highlight.kt | 2 +- .../quartz/encoders/Nip19Bech32.kt | 6 +- 6 files changed, 88 insertions(+), 43 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt index b68584e43..e191b980a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt @@ -25,6 +25,7 @@ import android.util.LruCache import androidx.compose.runtime.Stable import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.service.checkNotInMainThread +import com.vitorpamplona.amethyst.ui.note.njumpLink import com.vitorpamplona.ammolite.relays.BundledUpdate import com.vitorpamplona.ammolite.relays.Relay import com.vitorpamplona.ammolite.relays.RelayStats @@ -82,7 +83,7 @@ class AntiSpamFilter { logOffender(hash, event) if (relay != null) { - RelayStats.newSpam(relay.url, "https://njump.me/${Nip19Bech32.createNEvent(event.id, event.pubKey, event.kind, relay.url)}") + RelayStats.newSpam(relay.url, njumpLink(Nip19Bech32.createNEvent(event.id, event.pubKey, event.kind, relay.url))) } liveSpam.invalidateData() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt index 17c459e41..5688d557f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt @@ -41,6 +41,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.takeOrElse import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.Placeholder import androidx.compose.ui.text.PlaceholderVerticalAlign @@ -59,7 +60,7 @@ import coil.compose.AsyncImage import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.note.LoadChannel -import com.vitorpamplona.amethyst.ui.note.toShortenHex +import com.vitorpamplona.amethyst.ui.note.njumpLink import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.encoders.Nip19Bech32 @@ -80,12 +81,12 @@ fun ClickableRoute( nav: (String) -> Unit, ) { when (val entity = nip19.entity) { - is Nip19Bech32.NPub -> DisplayUser(entity.hex, nip19.additionalChars, accountViewModel, nav) - is Nip19Bech32.NProfile -> DisplayUser(entity.hex, nip19.additionalChars, accountViewModel, nav) - is Nip19Bech32.Note -> DisplayEvent(entity.hex, null, nip19.additionalChars, accountViewModel, nav) - is Nip19Bech32.NEvent -> DisplayEvent(entity.hex, entity.kind, nip19.additionalChars, accountViewModel, nav) + is Nip19Bech32.NPub -> DisplayUser(entity.hex, nip19.nip19raw, nip19.additionalChars, accountViewModel, nav) + is Nip19Bech32.NProfile -> DisplayUser(entity.hex, nip19.nip19raw, nip19.additionalChars, accountViewModel, nav) + is Nip19Bech32.Note -> DisplayEvent(entity.hex, null, nip19.nip19raw, nip19.additionalChars, accountViewModel, nav) + is Nip19Bech32.NEvent -> DisplayEvent(entity.hex, entity.kind, nip19.nip19raw, nip19.additionalChars, accountViewModel, nav) is Nip19Bech32.NEmbed -> LoadAndDisplayEvent(entity.event, nip19.additionalChars, accountViewModel, nav) - is Nip19Bech32.NAddress -> DisplayAddress(entity, nip19.additionalChars, accountViewModel, nav) + is Nip19Bech32.NAddress -> DisplayAddress(entity, nip19.nip19raw, nip19.additionalChars, accountViewModel, nav) is Nip19Bech32.NRelay -> { Text(word) } @@ -127,11 +128,16 @@ private fun LoadAndDisplayEvent( if (it != null) { DisplayNoteLink(it, event.id, event.kind, additionalChars, accountViewModel, nav) } else { + val externalLink = event.toNIP19() + val uri = LocalUriHandler.current + CreateClickableText( - clickablePart = remember(event.id) { "@${event.toNIP19()}" }, + clickablePart = "@$externalLink", suffix = additionalChars, - route = remember(event.id) { "Event/${event.id}" }, - nav = nav, + maxLines = 1, + onClick = { + runCatching { uri.openUri(njumpLink(externalLink)) } + }, ) } } @@ -141,6 +147,7 @@ private fun LoadAndDisplayEvent( fun DisplayEvent( hex: HexKey, kind: Int?, + nip19: String, additionalChars: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit, @@ -149,11 +156,16 @@ fun DisplayEvent( if (it != null) { DisplayNoteLink(it, hex, kind, additionalChars, accountViewModel, nav) } else { + val externalLink = njumpLink(nip19) + val uri = LocalUriHandler.current + CreateClickableText( - clickablePart = remember(hex) { "@${hex.toShortenHex()}" }, + clickablePart = remember(nip19) { "@$nip19" }, suffix = additionalChars, - route = remember(hex) { "Event/$hex" }, - nav = nav, + maxLines = 1, + onClick = { + runCatching { uri.openUri(externalLink) } + }, ) } } @@ -218,6 +230,7 @@ private fun DisplayNoteLink( @Composable private fun DisplayAddress( nip19: Nip19Bech32.NAddress, + originalNip19: String, additionalChars: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit, @@ -245,21 +258,23 @@ private fun DisplayAddress( } if (noteBase == null) { - if (additionalChars != null) { - Text( - remember { "@${nip19.atag}$additionalChars" }, - ) - } else { - Text( - remember { "@${nip19.atag}" }, - ) - } + val uri = LocalUriHandler.current + + CreateClickableText( + clickablePart = "@$originalNip19", + suffix = additionalChars, + maxLines = 1, + onClick = { + runCatching { uri.openUri(njumpLink(originalNip19)) } + }, + ) } } @Composable public fun DisplayUser( userHex: HexKey, + originalNip19: String, additionalChars: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit, @@ -280,15 +295,16 @@ public fun DisplayUser( userBase?.let { RenderUserAsClickableText(it, additionalChars, nav) } if (userBase == null) { - if (additionalChars != null) { - Text( - remember { "@${userHex}$additionalChars" }, - ) - } else { - Text( - remember { "@$userHex" }, - ) - } + val uri = LocalUriHandler.current + + CreateClickableText( + clickablePart = "@$originalNip19", + suffix = additionalChars, + maxLines = 1, + onClick = { + runCatching { uri.openUri(njumpLink(originalNip19)) } + }, + ) } } @@ -320,6 +336,26 @@ fun CreateClickableText( fontSize: TextUnit = TextUnit.Unspecified, route: String, nav: (String) -> Unit, +) { + CreateClickableText( + clickablePart, + suffix, + maxLines, + overrideColor, + fontWeight, + fontSize, + ) { nav(route) } +} + +@Composable +fun CreateClickableText( + clickablePart: String, + suffix: String?, + maxLines: Int = Int.MAX_VALUE, + overrideColor: Color? = null, + fontWeight: FontWeight? = null, + fontSize: TextUnit = TextUnit.Unspecified, + onClick: (Int) -> Unit, ) { val primaryColor = MaterialTheme.colorScheme.primary val onBackgroundColor = MaterialTheme.colorScheme.onBackground @@ -351,7 +387,8 @@ fun CreateClickableText( ClickableText( text = text, maxLines = maxLines, - onClick = { nav(route) }, + overflow = TextOverflow.Ellipsis, + onClick = onClick, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/MarkdownMediaRenderer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/MarkdownMediaRenderer.kt index e84e56592..8c15f7148 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/MarkdownMediaRenderer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/MarkdownMediaRenderer.kt @@ -162,8 +162,8 @@ class MarkdownMediaRenderer( } } else if (loadedLink?.nip19 != null) { when (val entity = loadedLink.nip19.entity) { - is Nip19Bech32.NPub -> renderObservableUser(entity.hex, richTextStringBuilder) - is Nip19Bech32.NProfile -> renderObservableUser(entity.hex, richTextStringBuilder) + is Nip19Bech32.NPub -> renderObservableUser(entity.hex, loadedLink.nip19.nip19raw, richTextStringBuilder) + is Nip19Bech32.NProfile -> renderObservableUser(entity.hex, loadedLink.nip19.nip19raw, richTextStringBuilder) is Nip19Bech32.Note -> renderObservableShortNoteUri(loadedLink, uri, richTextStringBuilder) is Nip19Bech32.NEvent -> renderObservableShortNoteUri(loadedLink, uri, richTextStringBuilder) is Nip19Bech32.NEmbed -> renderObservableShortNoteUri(loadedLink, uri, richTextStringBuilder) @@ -201,10 +201,11 @@ class MarkdownMediaRenderer( fun renderObservableUser( userHex: String, + nip19: String, richTextStringBuilder: RichTextString.Builder, ) { renderInline(richTextStringBuilder) { - DisplayUser(userHex, null, accountViewModel, nav) + DisplayUser(userHex, nip19, null, accountViewModel, nav) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt index ccff7de2a..fa0f979b8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt @@ -111,7 +111,11 @@ private fun lightenColor( } val externalLinkForUser = { user: User -> - "https://njump.me/${user.toNProfile()}" + njumpLink(user.toNProfile()) +} + +val njumpLink = { nip19BechAddress: String -> + "https://njump.me/$nip19BechAddress" } val externalLinkForNote = { note: Note -> @@ -119,17 +123,17 @@ val externalLinkForNote = { note: Note -> if (note.event?.getReward() != null) { "https://nostrbounties.com/b/${note.address().toNAddr()}" } else if (note.event is PeopleListEvent) { - "https://listr.lol/a/${note.address()?.toNAddr()}" + "https://listr.lol/a/${note.address().toNAddr()}" } else if (note.event is AudioTrackEvent) { - "https://zapstr.live/?track=${note.address()?.toNAddr()}" + "https://zapstr.live/?track=${note.address().toNAddr()}" } else { - "https://njump.me/${note.address()?.toNAddr()}" + njumpLink(note.address().toNAddr()) } } else { if (note.event is FileHeaderEvent) { "https://filestr.vercel.app/e/${note.toNEvent()}" } else { - "https://njump.me/${note.toNEvent()}" + njumpLink(note.toNEvent()) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Highlight.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Highlight.kt index 495468651..9a1a85702 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Highlight.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Highlight.kt @@ -257,7 +257,7 @@ fun DisplayEntryForNote( style = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.primary), ) } else { - DisplayEvent(noteEvent.id, noteEvent.kind, "", accountViewModel, nav) + DisplayEvent(noteEvent.id, noteEvent.kind, noteEvent.toNIP19(), null, accountViewModel, nav) } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/encoders/Nip19Bech32.kt b/quartz/src/main/java/com/vitorpamplona/quartz/encoders/Nip19Bech32.kt index 951bfaa83..ee4f57a26 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/encoders/Nip19Bech32.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/encoders/Nip19Bech32.kt @@ -57,6 +57,7 @@ object Nip19Bech32 { @Immutable data class ParseReturn( val entity: Entity, + val nip19raw: String, val additionalChars: String? = null, ) @@ -138,7 +139,8 @@ object Nip19Bech32 { additionalChars: String?, ): ParseReturn? = try { - val bytes = (type + key).bechToBytes() + val nip19 = (type + key) + val bytes = nip19.bechToBytes() when (type.lowercase()) { "nsec1" -> nsec(bytes) @@ -151,7 +153,7 @@ object Nip19Bech32 { "nembed1" -> nembed(bytes) else -> null }?.let { - ParseReturn(it, additionalChars) + ParseReturn(it, nip19, additionalChars) } } catch (e: Throwable) { Log.w("NIP19 Parser", "Issue trying to Decode NIP19 $key: ${e.message}", e)