From 5e417dd890147a08f17c23bfb16d5540eaaf3781 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 25 Jun 2024 18:19:14 -0400 Subject: [PATCH] Fixes the rendering of highlights when no user is present and includes options to render by `e` tags --- .../amethyst/ui/components/ClickableRoute.kt | 4 +- .../amethyst/ui/note/types/Highlight.kt | 206 ++++++++++++++---- .../com/vitorpamplona/quartz/events/Event.kt | 2 + .../quartz/events/EventInterface.kt | 2 + .../quartz/events/HighlightEvent.kt | 2 + 5 files changed, 175 insertions(+), 41 deletions(-) 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 dd7cbea9a..17c459e41 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 @@ -138,7 +138,7 @@ private fun LoadAndDisplayEvent( } @Composable -private fun DisplayEvent( +fun DisplayEvent( hex: HexKey, kind: Int?, additionalChars: String?, @@ -293,7 +293,7 @@ public fun DisplayUser( } @Composable -private fun RenderUserAsClickableText( +public fun RenderUserAsClickableText( baseUser: User, additionalChars: String?, nav: (String) -> Unit, 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 20a7cc275..5c5e08412 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 @@ -40,19 +40,27 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.AnnotatedString +import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.components.ClickableUrl import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji +import com.vitorpamplona.amethyst.ui.components.DisplayEvent +import com.vitorpamplona.amethyst.ui.components.LoadNote +import com.vitorpamplona.amethyst.ui.components.RenderUserAsClickableText import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.components.measureSpaceWidth import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.quartz.encoders.ATag +import com.vitorpamplona.quartz.encoders.HexKey +import com.vitorpamplona.quartz.events.BaseTextNoteEvent import com.vitorpamplona.quartz.events.EmptyTagList import com.vitorpamplona.quartz.events.HighlightEvent import com.vitorpamplona.quartz.events.LongTextNoteEvent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.net.URL @Composable @@ -72,6 +80,7 @@ fun RenderHighlight( authorHex = noteEvent.author(), url = noteEvent.inUrl(), postAddress = noteEvent.inPost(), + postVersion = noteEvent.inPostVersion(), makeItShort = makeItShort, canPreview = canPreview, quotesLeft = quotesLeft, @@ -81,12 +90,14 @@ fun RenderHighlight( ) } +@OptIn(ExperimentalLayoutApi::class) @Composable fun DisplayHighlight( highlight: String, authorHex: String?, url: String?, postAddress: ATag?, + postVersion: HexKey?, makeItShort: Boolean, canPreview: Boolean, quotesLeft: Int, @@ -112,54 +123,151 @@ fun DisplayHighlight( nav = nav, ) - DisplayQuoteAuthor(authorHex ?: "", url, postAddress, accountViewModel, nav) -} - -@OptIn(ExperimentalLayoutApi::class) -@Composable -private fun DisplayQuoteAuthor( - authorHex: String, - url: String?, - postAddress: ATag?, - accountViewModel: AccountViewModel, - nav: (String) -> Unit, -) { - var userBase by remember { mutableStateOf(accountViewModel.getUserIfExists(authorHex)) } - - if (userBase == null) { - LaunchedEffect(Unit) { - accountViewModel.checkGetOrCreateUser(authorHex) { newUserBase -> - userBase = newUserBase - } - } - } - val spaceWidth = measureSpaceWidth(textStyle = LocalTextStyle.current) FlowRow( horizontalArrangement = Arrangement.spacedBy(spaceWidth), verticalArrangement = Arrangement.Center, ) { - userBase?.let { userBase -> - val userMetadata by userBase.live().userMetadataInfo.observeAsState() - - CreateClickableTextWithEmoji( - clickablePart = userMetadata?.bestName() ?: userBase.pubkeyDisplayHex(), - maxLines = 1, - route = "User/${userBase.pubkeyHex}", - nav = nav, - tags = userMetadata?.tags, - ) - } - - url?.let { url -> LoadAndDisplayUrl(url) } - - postAddress?.let { address -> LoadAndDisplayPost(address, accountViewModel, nav) } + DisplayQuoteAuthor( + authorHex = authorHex, + url = url, + postAddress = postAddress, + postVersion = postVersion, + accountViewModel = accountViewModel, + nav = nav, + ) } } @Composable -fun LoadAndDisplayUrl(url: String) { +private fun DisplayQuoteAuthor( + authorHex: String?, + url: String?, + postAddress: ATag?, + postVersion: HexKey?, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + var userBase by remember { mutableStateOf(authorHex?.let { accountViewModel.getUserIfExists(it) }) } + + if (userBase == null && authorHex != null) { + LaunchedEffect(authorHex) { + accountViewModel.checkGetOrCreateUser(authorHex) { newUserBase -> + userBase = newUserBase + } + } + } + + var addressable by remember { + mutableStateOf(postAddress?.let { accountViewModel.getAddressableNoteIfExists(it.toTag()) }) + } + + if (addressable == null && postAddress != null) { + LaunchedEffect(key1 = postAddress) { + val newNote = + withContext(Dispatchers.IO) { + accountViewModel.getOrCreateAddressableNote(postAddress) + } + if (addressable != newNote) { + addressable = newNote + } + } + } + + var version by remember { + mutableStateOf(postVersion?.let { accountViewModel.getNoteIfExists(it) }) + } + + if (version == null && postVersion != null) { + LaunchedEffect(key1 = postVersion) { + val newNote = + withContext(Dispatchers.IO) { + accountViewModel.getOrCreateNote(postVersion) + } + if (version != newNote) { + version = newNote + } + } + } + + if (addressable != null) { + addressable?.let { + DisplayEntryForNote(it, userBase, accountViewModel, nav) + } + } else if (version != null) { + version?.let { + DisplayEntryForNote(it, userBase, accountViewModel, nav) + } + } else if (url != null) { + DisplayEntryForAUrl(url, userBase, accountViewModel, nav) + } else if (userBase != null) { + userBase?.let { + DisplayEntryForUser(it, accountViewModel, nav) + } + } +} + +@Composable +fun DisplayEntryForUser( + userBase: User, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + val userMetadata by userBase.live().userMetadataInfo.observeAsState() + + CreateClickableTextWithEmoji( + clickablePart = userMetadata?.bestName() ?: userBase.pubkeyDisplayHex(), + maxLines = 1, + route = "User/${userBase.pubkeyHex}", + nav = nav, + tags = userMetadata?.tags, + ) +} + +@Composable +fun DisplayEntryForNote( + note: Note, + userBase: User?, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + val noteState by note.live().metadata.observeAsState() + + val author = userBase ?: noteState?.note?.author + + if (author != null) { + RenderUserAsClickableText(author, null, nav) + } + + val noteEvent = noteState?.note?.event as? BaseTextNoteEvent ?: return + + val description = noteEvent.firstTagFor("title", "subject", "alt") + + Text("-", maxLines = 1) + + if (description != null) { + ClickableText( + text = AnnotatedString(description), + onClick = { routeFor(note, accountViewModel.userProfile())?.let { nav(it) } }, + style = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.primary), + ) + } else { + DisplayEvent(noteEvent.id, noteEvent.kind, "", accountViewModel, nav) + } +} + +@Composable +fun DisplayEntryForAUrl( + url: String, + userBase: User?, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + if (userBase != null) { + DisplayEntryForUser(userBase, accountViewModel, nav) + } + val validatedUrl = remember { try { @@ -171,7 +279,9 @@ fun LoadAndDisplayUrl(url: String) { } validatedUrl?.host?.let { host -> - Text("-", maxLines = 1) + if (userBase != null) { + Text("-", maxLines = 1) + } ClickableUrl(urlText = host, url = url) } } @@ -198,3 +308,21 @@ private fun LoadAndDisplayPost( } } } + +@Composable +private fun LoadAndDisplayPostVersion( + postEvent: HexKey, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + LoadNote(baseNoteHex = postEvent, accountViewModel) { baseNote -> + baseNote?.let { note -> + val noteState by note.live().metadata.observeAsState() + val noteEvent = noteState?.note?.event as? BaseTextNoteEvent ?: return@LoadNote + + Text("-", maxLines = 1) + + DisplayEvent(noteEvent.id, noteEvent.kind, "", accountViewModel, nav) + } + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt index 5dcd4710a..19521379e 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/Event.kt @@ -121,6 +121,8 @@ open class Event( override fun taggedUrls() = tags.filter { it.size > 1 && it[0] == "r" }.map { it[1] } + override fun firstTagFor(vararg key: String) = tags.firstOrNull { it.size > 1 && it[0] in key }?.let { it[1] } + override fun firstTaggedUser() = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.let { it[1] } override fun firstTaggedEvent() = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.let { it[1] } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt index 5465a50f3..db11a3ea4 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/EventInterface.kt @@ -133,6 +133,8 @@ interface EventInterface { fun taggedUrls(): List + fun firstTagFor(vararg key: String): String? + fun firstTaggedAddress(): ATag? fun firstTaggedUser(): HexKey? diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/HighlightEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/HighlightEvent.kt index 5f6253e84..398e46fbc 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/HighlightEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/HighlightEvent.kt @@ -42,6 +42,8 @@ class HighlightEvent( fun inPost() = firstTaggedAddress() + fun inPostVersion() = firstTaggedEvent() + companion object { const val KIND = 9802 const val ALT = "Highlight/quote event"