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
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,

View File

@ -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<User?>(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<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 =
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)
}
}
}

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 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] }

View File

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

View File

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