Displays links in the Status if they are present.

This commit is contained in:
Vitor Pamplona 2023-08-24 17:58:34 -04:00
parent 5b596d2a56
commit b57dd98059
8 changed files with 128 additions and 16 deletions

View File

@ -6,9 +6,12 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.OpenInNew
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
@ -31,14 +34,19 @@ import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.Nip05Verifier
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
import com.vitorpamplona.amethyst.ui.note.LoadStatuses
import com.vitorpamplona.amethyst.ui.note.NIP05CheckingIcon
import com.vitorpamplona.amethyst.ui.note.NIP05FailedVerification
import com.vitorpamplona.amethyst.ui.note.NIP05VerifiedIcon
import com.vitorpamplona.amethyst.ui.note.routeFor
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.Font14SP
import com.vitorpamplona.amethyst.ui.theme.NIP05IconSize
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
import com.vitorpamplona.amethyst.ui.theme.Size16Modifier
import com.vitorpamplona.amethyst.ui.theme.Size18Modifier
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
import com.vitorpamplona.amethyst.ui.theme.nip05
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.events.AddressableEvent
@ -105,21 +113,31 @@ fun nip05VerificationAsAState(userMetadata: UserMetadata, pubkeyHex: String): Mu
}
@Composable
fun ObserveDisplayNip05Status(baseNote: Note, columnModifier: Modifier = Modifier) {
fun ObserveDisplayNip05Status(
baseNote: Note,
columnModifier: Modifier = Modifier,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val author by baseNote.live().authorChanges.observeAsState()
author?.let {
ObserveDisplayNip05Status(it, columnModifier)
ObserveDisplayNip05Status(it, columnModifier, accountViewModel, nav)
}
}
@Composable
fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifier) {
fun ObserveDisplayNip05Status(
baseUser: User,
columnModifier: Modifier = Modifier,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val nip05 by baseUser.live().nip05Changes.observeAsState(baseUser.nip05())
LoadStatuses(baseUser) { statuses ->
Crossfade(targetState = nip05, modifier = columnModifier, label = "ObserveDisplayNip05StatusCrossfade") {
VerifyAndDisplayNIP05OrStatusLine(it, statuses, baseUser, columnModifier)
VerifyAndDisplayNIP05OrStatusLine(it, statuses, baseUser, columnModifier, accountViewModel, nav)
}
}
}
@ -129,7 +147,9 @@ private fun VerifyAndDisplayNIP05OrStatusLine(
nip05: String?,
statuses: ImmutableList<AddressableNote>,
baseUser: User,
columnModifier: Modifier = Modifier
columnModifier: Modifier = Modifier,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
Column(modifier = columnModifier) {
Row(verticalAlignment = Alignment.CenterVertically) {
@ -139,13 +159,13 @@ private fun VerifyAndDisplayNIP05OrStatusLine(
if (nip05Verified.value != true) {
DisplayNIP05(nip05, nip05Verified)
} else if (!statuses.isEmpty()) {
RotateStatuses(statuses)
RotateStatuses(statuses, accountViewModel, nav)
} else {
DisplayNIP05(nip05, nip05Verified)
}
} else {
if (!statuses.isEmpty()) {
RotateStatuses(statuses)
RotateStatuses(statuses, accountViewModel, nav)
} else {
DisplayUsersNpub(baseUser.pubkeyDisplayHex())
}
@ -155,12 +175,16 @@ private fun VerifyAndDisplayNIP05OrStatusLine(
}
@Composable
fun RotateStatuses(statuses: ImmutableList<AddressableNote>) {
fun RotateStatuses(
statuses: ImmutableList<AddressableNote>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
var indexToDisplay by remember {
mutableIntStateOf(0)
}
DisplayStatus(statuses[indexToDisplay])
DisplayStatus(statuses[indexToDisplay], accountViewModel, nav)
if (statuses.size > 1) {
LaunchedEffect(Unit) {
@ -184,13 +208,26 @@ fun DisplayUsersNpub(npub: String) {
}
@Composable
fun DisplayStatus(addressableNote: AddressableNote) {
fun DisplayStatus(
addressableNote: AddressableNote,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val noteState by addressableNote.live().metadata.observeAsState()
val content = remember(noteState) { addressableNote.event?.content() ?: "" }
val type = remember(noteState) {
(addressableNote.event as? AddressableEvent)?.dTag() ?: ""
}
val url = remember(noteState) {
addressableNote.event?.firstTaggedUrl()?.ifBlank { null }
}
val nostrATag = remember(noteState) {
addressableNote.event?.firstTaggedAddress()
}
val nostrHexID = remember(noteState) {
addressableNote.event?.firstTaggedEvent()?.ifBlank { null }
}
when (type) {
"music" -> Icon(
@ -209,6 +246,63 @@ fun DisplayStatus(addressableNote: AddressableNote) {
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
if (url != null) {
val uri = LocalUriHandler.current
IconButton(
modifier = Size15Modifier,
onClick = { runCatching { uri.openUri(url.trim()) } }
) {
Icon(
imageVector = Icons.Default.OpenInNew,
null,
modifier = Size15Modifier,
tint = MaterialTheme.colors.lessImportantLink
)
}
} else if (nostrATag != null) {
LoadAddressableNote(nostrATag) { note ->
if (note != null) {
IconButton(
modifier = Size15Modifier,
onClick = {
routeFor(
note,
accountViewModel.userProfile()
)?.let { nav(it) }
}
) {
Icon(
imageVector = Icons.Default.OpenInNew,
null,
modifier = Size15Modifier,
tint = MaterialTheme.colors.lessImportantLink
)
}
}
}
} else if (nostrHexID != null) {
LoadNote(baseNoteHex = nostrHexID) {
if (it != null) {
IconButton(
modifier = Size15Modifier,
onClick = {
routeFor(
it,
accountViewModel.userProfile()
)?.let { nav(it) }
}
) {
Icon(
imageVector = Icons.Default.OpenInNew,
null,
modifier = Size15Modifier,
tint = MaterialTheme.colors.lessImportantLink
)
}
}
}
}
}
@Composable

View File

@ -2487,7 +2487,7 @@ fun SecondUserInfoRow(
val noteAuthor = remember { note.author } ?: return
Row(verticalAlignment = CenterVertically, modifier = UserNameMaxRowHeight) {
ObserveDisplayNip05Status(noteAuthor, remember { Modifier.weight(1f) })
ObserveDisplayNip05Status(noteAuthor, remember { Modifier.weight(1f) }, accountViewModel, nav)
val geo = remember { noteEvent.getGeoHash() }
if (geo != null) {

View File

@ -328,7 +328,7 @@ fun NoteMaster(
}
Row(verticalAlignment = Alignment.CenterVertically) {
ObserveDisplayNip05Status(baseNote, remember { Modifier.weight(1f) })
ObserveDisplayNip05Status(baseNote, remember { Modifier.weight(1f) }, accountViewModel, nav)
val geo = remember { noteEvent.getGeoHash() }
if (geo != null) {

View File

@ -504,7 +504,7 @@ fun ChatroomHeader(
Column(modifier = Modifier.padding(start = 10.dp)) {
UsernameDisplay(baseUser)
ObserveDisplayNip05Status(baseUser)
ObserveDisplayNip05Status(baseUser, accountViewModel = accountViewModel, nav = nav)
}
}
}

View File

@ -374,7 +374,9 @@ private fun RenderAuthorInformation(
Row(verticalAlignment = Alignment.CenterVertically) {
ObserveDisplayNip05Status(
remember { note.author!! },
remember { Modifier.weight(1f) }
remember { Modifier.weight(1f) },
accountViewModel,
nav = nav
)
}
Row(

View File

@ -69,6 +69,16 @@ open class Event(
override fun taggedUrls() = tags.filter { it.size > 1 && it[0] == "r" }.map { 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 firstTaggedUrl() = tags.firstOrNull { it.size > 1 && it[0] == "r" }?.let { it[1] }
override fun firstTaggedAddress() = tags.firstOrNull { it.size > 1 && it[0] == "r" }?.let {
val aTagValue = it[1]
val relay = it.getOrNull(2)
ATag.parse(aTagValue, relay)
}
override fun taggedEmojis() = tags.filter { it.size > 2 && it[0] == "emoji" }.map { EmojiUrl(it[1], it[2]) }
override fun isSensitive() = tags.any {

View File

@ -68,6 +68,11 @@ interface EventInterface {
fun taggedEvents(): List<HexKey>
fun taggedUrls(): List<String>
fun firstTaggedAddress(): ATag?
fun firstTaggedUser(): HexKey?
fun firstTaggedEvent(): HexKey?
fun firstTaggedUrl(): String?
fun taggedEmojis(): List<EmojiUrl>
fun matchTag1With(text: String): Boolean
fun isExpired(): Boolean

View File

@ -44,10 +44,11 @@ class StatusEvent(
privateKey: ByteArray,
createdAt: Long = TimeUtils.now()
): StatusEvent {
val tags = event.tags.plus(listOf(listOf("r", "http://amethyst.social")))
val pubKey = event.pubKey()
val id = generateId(pubKey, createdAt, kind, event.tags, newStatus)
val id = generateId(pubKey, createdAt, kind, tags, newStatus)
val sig = CryptoUtils.sign(id, privateKey)
return StatusEvent(id.toHexKey(), pubKey, createdAt, event.tags, newStatus, sig.toHexKey())
return StatusEvent(id.toHexKey(), pubKey, createdAt, tags, newStatus, sig.toHexKey())
}
}
}