Fixes the rendering of highlights when no user is present and includes options to render by e tags

This commit is contained in:
Vitor Pamplona 2024-06-25 18:19:14 -04:00
parent 95694ac355
commit 5e417dd890
5 changed files with 175 additions and 41 deletions

View File

@ -138,7 +138,7 @@ private fun LoadAndDisplayEvent(
} }
@Composable @Composable
private fun DisplayEvent( fun DisplayEvent(
hex: HexKey, hex: HexKey,
kind: Int?, kind: Int?,
additionalChars: String?, additionalChars: String?,
@ -293,7 +293,7 @@ public fun DisplayUser(
} }
@Composable @Composable
private fun RenderUserAsClickableText( public fun RenderUserAsClickableText(
baseUser: User, baseUser: User,
additionalChars: String?, additionalChars: String?,
nav: (String) -> Unit, nav: (String) -> Unit,

View File

@ -40,19 +40,27 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.components.ClickableUrl import com.vitorpamplona.amethyst.ui.components.ClickableUrl
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji 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.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.components.measureSpaceWidth import com.vitorpamplona.amethyst.ui.components.measureSpaceWidth
import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.navigation.routeFor
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.quartz.encoders.ATag 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.EmptyTagList
import com.vitorpamplona.quartz.events.HighlightEvent import com.vitorpamplona.quartz.events.HighlightEvent
import com.vitorpamplona.quartz.events.LongTextNoteEvent import com.vitorpamplona.quartz.events.LongTextNoteEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.net.URL import java.net.URL
@Composable @Composable
@ -72,6 +80,7 @@ fun RenderHighlight(
authorHex = noteEvent.author(), authorHex = noteEvent.author(),
url = noteEvent.inUrl(), url = noteEvent.inUrl(),
postAddress = noteEvent.inPost(), postAddress = noteEvent.inPost(),
postVersion = noteEvent.inPostVersion(),
makeItShort = makeItShort, makeItShort = makeItShort,
canPreview = canPreview, canPreview = canPreview,
quotesLeft = quotesLeft, quotesLeft = quotesLeft,
@ -81,12 +90,14 @@ fun RenderHighlight(
) )
} }
@OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
fun DisplayHighlight( fun DisplayHighlight(
highlight: String, highlight: String,
authorHex: String?, authorHex: String?,
url: String?, url: String?,
postAddress: ATag?, postAddress: ATag?,
postVersion: HexKey?,
makeItShort: Boolean, makeItShort: Boolean,
canPreview: Boolean, canPreview: Boolean,
quotesLeft: Int, quotesLeft: Int,
@ -112,54 +123,151 @@ fun DisplayHighlight(
nav = nav, 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<User?>(accountViewModel.getUserIfExists(authorHex)) }
if (userBase == null) {
LaunchedEffect(Unit) {
accountViewModel.checkGetOrCreateUser(authorHex) { newUserBase ->
userBase = newUserBase
}
}
}
val spaceWidth = measureSpaceWidth(textStyle = LocalTextStyle.current) val spaceWidth = measureSpaceWidth(textStyle = LocalTextStyle.current)
FlowRow( FlowRow(
horizontalArrangement = Arrangement.spacedBy(spaceWidth), horizontalArrangement = Arrangement.spacedBy(spaceWidth),
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
) { ) {
userBase?.let { userBase -> DisplayQuoteAuthor(
val userMetadata by userBase.live().userMetadataInfo.observeAsState() authorHex = authorHex,
url = url,
CreateClickableTextWithEmoji( postAddress = postAddress,
clickablePart = userMetadata?.bestName() ?: userBase.pubkeyDisplayHex(), postVersion = postVersion,
maxLines = 1, accountViewModel = accountViewModel,
route = "User/${userBase.pubkeyHex}", nav = nav,
nav = nav, )
tags = userMetadata?.tags,
)
}
url?.let { url -> LoadAndDisplayUrl(url) }
postAddress?.let { address -> LoadAndDisplayPost(address, accountViewModel, nav) }
} }
} }
@Composable @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<User?>(authorHex?.let { accountViewModel.getUserIfExists(it) }) }
if (userBase == null && authorHex != null) {
LaunchedEffect(authorHex) {
accountViewModel.checkGetOrCreateUser(authorHex) { newUserBase ->
userBase = newUserBase
}
}
}
var addressable by remember {
mutableStateOf<AddressableNote?>(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<Note?>(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 = val validatedUrl =
remember { remember {
try { try {
@ -171,7 +279,9 @@ fun LoadAndDisplayUrl(url: String) {
} }
validatedUrl?.host?.let { host -> validatedUrl?.host?.let { host ->
Text("-", maxLines = 1) if (userBase != null) {
Text("-", maxLines = 1)
}
ClickableUrl(urlText = host, url = url) 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)
}
}
}

View File

@ -121,6 +121,8 @@ open class Event(
override fun taggedUrls() = tags.filter { it.size > 1 && it[0] == "r" }.map { it[1] } 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 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] } override fun firstTaggedEvent() = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.let { it[1] }

View File

@ -133,6 +133,8 @@ interface EventInterface {
fun taggedUrls(): List<String> fun taggedUrls(): List<String>
fun firstTagFor(vararg key: String): String?
fun firstTaggedAddress(): ATag? fun firstTaggedAddress(): ATag?
fun firstTaggedUser(): HexKey? fun firstTaggedUser(): HexKey?

View File

@ -42,6 +42,8 @@ class HighlightEvent(
fun inPost() = firstTaggedAddress() fun inPost() = firstTaggedAddress()
fun inPostVersion() = firstTaggedEvent()
companion object { companion object {
const val KIND = 9802 const val KIND = 9802
const val ALT = "Highlight/quote event" const val ALT = "Highlight/quote event"