Displays links in the Status if they are present.
This commit is contained in:
@@ -6,9 +6,12 @@ import androidx.compose.foundation.layout.Row
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.text.ClickableText
|
import androidx.compose.foundation.text.ClickableText
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.LocalTextStyle
|
import androidx.compose.material.LocalTextStyle
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
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.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.Nip05Verifier
|
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.LoadStatuses
|
||||||
import com.vitorpamplona.amethyst.ui.note.NIP05CheckingIcon
|
import com.vitorpamplona.amethyst.ui.note.NIP05CheckingIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.NIP05FailedVerification
|
import com.vitorpamplona.amethyst.ui.note.NIP05FailedVerification
|
||||||
import com.vitorpamplona.amethyst.ui.note.NIP05VerifiedIcon
|
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.Font14SP
|
||||||
import com.vitorpamplona.amethyst.ui.theme.NIP05IconSize
|
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.Size16Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size18Modifier
|
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.nip05
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import com.vitorpamplona.quartz.events.AddressableEvent
|
import com.vitorpamplona.quartz.events.AddressableEvent
|
||||||
@@ -105,21 +113,31 @@ fun nip05VerificationAsAState(userMetadata: UserMetadata, pubkeyHex: String): Mu
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@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()
|
val author by baseNote.live().authorChanges.observeAsState()
|
||||||
|
|
||||||
author?.let {
|
author?.let {
|
||||||
ObserveDisplayNip05Status(it, columnModifier)
|
ObserveDisplayNip05Status(it, columnModifier, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@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())
|
val nip05 by baseUser.live().nip05Changes.observeAsState(baseUser.nip05())
|
||||||
|
|
||||||
LoadStatuses(baseUser) { statuses ->
|
LoadStatuses(baseUser) { statuses ->
|
||||||
Crossfade(targetState = nip05, modifier = columnModifier, label = "ObserveDisplayNip05StatusCrossfade") {
|
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?,
|
nip05: String?,
|
||||||
statuses: ImmutableList<AddressableNote>,
|
statuses: ImmutableList<AddressableNote>,
|
||||||
baseUser: User,
|
baseUser: User,
|
||||||
columnModifier: Modifier = Modifier
|
columnModifier: Modifier = Modifier,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
Column(modifier = columnModifier) {
|
Column(modifier = columnModifier) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
@@ -139,13 +159,13 @@ private fun VerifyAndDisplayNIP05OrStatusLine(
|
|||||||
if (nip05Verified.value != true) {
|
if (nip05Verified.value != true) {
|
||||||
DisplayNIP05(nip05, nip05Verified)
|
DisplayNIP05(nip05, nip05Verified)
|
||||||
} else if (!statuses.isEmpty()) {
|
} else if (!statuses.isEmpty()) {
|
||||||
RotateStatuses(statuses)
|
RotateStatuses(statuses, accountViewModel, nav)
|
||||||
} else {
|
} else {
|
||||||
DisplayNIP05(nip05, nip05Verified)
|
DisplayNIP05(nip05, nip05Verified)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!statuses.isEmpty()) {
|
if (!statuses.isEmpty()) {
|
||||||
RotateStatuses(statuses)
|
RotateStatuses(statuses, accountViewModel, nav)
|
||||||
} else {
|
} else {
|
||||||
DisplayUsersNpub(baseUser.pubkeyDisplayHex())
|
DisplayUsersNpub(baseUser.pubkeyDisplayHex())
|
||||||
}
|
}
|
||||||
@@ -155,12 +175,16 @@ private fun VerifyAndDisplayNIP05OrStatusLine(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RotateStatuses(statuses: ImmutableList<AddressableNote>) {
|
fun RotateStatuses(
|
||||||
|
statuses: ImmutableList<AddressableNote>,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
var indexToDisplay by remember {
|
var indexToDisplay by remember {
|
||||||
mutableIntStateOf(0)
|
mutableIntStateOf(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayStatus(statuses[indexToDisplay])
|
DisplayStatus(statuses[indexToDisplay], accountViewModel, nav)
|
||||||
|
|
||||||
if (statuses.size > 1) {
|
if (statuses.size > 1) {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
@@ -184,13 +208,26 @@ fun DisplayUsersNpub(npub: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DisplayStatus(addressableNote: AddressableNote) {
|
fun DisplayStatus(
|
||||||
|
addressableNote: AddressableNote,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
val noteState by addressableNote.live().metadata.observeAsState()
|
val noteState by addressableNote.live().metadata.observeAsState()
|
||||||
|
|
||||||
val content = remember(noteState) { addressableNote.event?.content() ?: "" }
|
val content = remember(noteState) { addressableNote.event?.content() ?: "" }
|
||||||
val type = remember(noteState) {
|
val type = remember(noteState) {
|
||||||
(addressableNote.event as? AddressableEvent)?.dTag() ?: ""
|
(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) {
|
when (type) {
|
||||||
"music" -> Icon(
|
"music" -> Icon(
|
||||||
@@ -209,6 +246,63 @@ fun DisplayStatus(addressableNote: AddressableNote) {
|
|||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
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
|
@Composable
|
||||||
|
|||||||
@@ -2487,7 +2487,7 @@ fun SecondUserInfoRow(
|
|||||||
val noteAuthor = remember { note.author } ?: return
|
val noteAuthor = remember { note.author } ?: return
|
||||||
|
|
||||||
Row(verticalAlignment = CenterVertically, modifier = UserNameMaxRowHeight) {
|
Row(verticalAlignment = CenterVertically, modifier = UserNameMaxRowHeight) {
|
||||||
ObserveDisplayNip05Status(noteAuthor, remember { Modifier.weight(1f) })
|
ObserveDisplayNip05Status(noteAuthor, remember { Modifier.weight(1f) }, accountViewModel, nav)
|
||||||
|
|
||||||
val geo = remember { noteEvent.getGeoHash() }
|
val geo = remember { noteEvent.getGeoHash() }
|
||||||
if (geo != null) {
|
if (geo != null) {
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ fun NoteMaster(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
ObserveDisplayNip05Status(baseNote, remember { Modifier.weight(1f) })
|
ObserveDisplayNip05Status(baseNote, remember { Modifier.weight(1f) }, accountViewModel, nav)
|
||||||
|
|
||||||
val geo = remember { noteEvent.getGeoHash() }
|
val geo = remember { noteEvent.getGeoHash() }
|
||||||
if (geo != null) {
|
if (geo != null) {
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ fun ChatroomHeader(
|
|||||||
|
|
||||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||||
UsernameDisplay(baseUser)
|
UsernameDisplay(baseUser)
|
||||||
ObserveDisplayNip05Status(baseUser)
|
ObserveDisplayNip05Status(baseUser, accountViewModel = accountViewModel, nav = nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -374,7 +374,9 @@ private fun RenderAuthorInformation(
|
|||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
ObserveDisplayNip05Status(
|
ObserveDisplayNip05Status(
|
||||||
remember { note.author!! },
|
remember { note.author!! },
|
||||||
remember { Modifier.weight(1f) }
|
remember { Modifier.weight(1f) },
|
||||||
|
accountViewModel,
|
||||||
|
nav = nav
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
@@ -69,6 +69,16 @@ 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 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 taggedEmojis() = tags.filter { it.size > 2 && it[0] == "emoji" }.map { EmojiUrl(it[1], it[2]) }
|
||||||
|
|
||||||
override fun isSensitive() = tags.any {
|
override fun isSensitive() = tags.any {
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ interface EventInterface {
|
|||||||
fun taggedEvents(): List<HexKey>
|
fun taggedEvents(): List<HexKey>
|
||||||
fun taggedUrls(): List<String>
|
fun taggedUrls(): List<String>
|
||||||
|
|
||||||
|
fun firstTaggedAddress(): ATag?
|
||||||
|
fun firstTaggedUser(): HexKey?
|
||||||
|
fun firstTaggedEvent(): HexKey?
|
||||||
|
fun firstTaggedUrl(): String?
|
||||||
|
|
||||||
fun taggedEmojis(): List<EmojiUrl>
|
fun taggedEmojis(): List<EmojiUrl>
|
||||||
fun matchTag1With(text: String): Boolean
|
fun matchTag1With(text: String): Boolean
|
||||||
fun isExpired(): Boolean
|
fun isExpired(): Boolean
|
||||||
|
|||||||
@@ -44,10 +44,11 @@ class StatusEvent(
|
|||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = TimeUtils.now()
|
createdAt: Long = TimeUtils.now()
|
||||||
): StatusEvent {
|
): StatusEvent {
|
||||||
|
val tags = event.tags.plus(listOf(listOf("r", "http://amethyst.social")))
|
||||||
val pubKey = event.pubKey()
|
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)
|
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user