Merge pull request #1827 from vitorpamplona/claude/implement-nip-38-q8DOF
Enhance user status display with emoji support and metadata tags
This commit is contained in:
+58
-8
@@ -59,9 +59,12 @@ import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNo
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserStatuses
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableTextPrimary
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeForUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
@@ -77,8 +80,13 @@ import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.aTag.firstTaggedAddress
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.firstTaggedEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.firstTaggedUserId
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.taggedEmojis
|
||||
import com.vitorpamplona.quartz.nip38UserStatus.StatusEvent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@@ -220,12 +228,24 @@ fun DisplayStatus(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val emojis =
|
||||
remember(event) {
|
||||
val emojiList = event.taggedEmojis()
|
||||
if (emojiList.isEmpty()) {
|
||||
persistentMapOf()
|
||||
} else {
|
||||
emojiList.associate { it.code to it.url }.toImmutableMap()
|
||||
}
|
||||
}
|
||||
|
||||
DisplayStatusInner(
|
||||
event.content,
|
||||
event.dTag(),
|
||||
event.firstTaggedUrl()?.ifBlank { null },
|
||||
event.firstTaggedAddress(),
|
||||
event.firstTaggedEvent(),
|
||||
event.firstTaggedUserId(),
|
||||
emojis,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
@@ -238,11 +258,13 @@ fun DisplayStatusInner(
|
||||
url: String?,
|
||||
nostrATag: Address?,
|
||||
nostrETag: ETag?,
|
||||
nostrPTag: String?,
|
||||
emojis: ImmutableMap<String, String>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
when (type) {
|
||||
"music" -> {
|
||||
StatusEvent.MUSIC -> {
|
||||
Icon(
|
||||
imageVector = CustomHashTagIcons.Tunestr,
|
||||
null,
|
||||
@@ -254,13 +276,24 @@ fun DisplayStatusInner(
|
||||
else -> {}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = content,
|
||||
fontSize = Font14SP,
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
if (emojis.isNotEmpty()) {
|
||||
CreateTextWithEmoji(
|
||||
text = content,
|
||||
emojis = emojis,
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
fontSize = Font14SP,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = content,
|
||||
fontSize = Font14SP,
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
|
||||
if (url != null) {
|
||||
val uri = LocalUriHandler.current
|
||||
@@ -320,6 +353,23 @@ fun DisplayStatusInner(
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (nostrPTag != null) {
|
||||
LoadUser(baseUserHex = nostrPTag, accountViewModel) { user ->
|
||||
if (user != null) {
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
IconButton(
|
||||
modifier = Size15Modifier,
|
||||
onClick = { nav.nav(routeForUser(nostrPTag)) },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.OpenInNew,
|
||||
null,
|
||||
modifier = Size15Modifier,
|
||||
tint = MaterialTheme.colorScheme.lessImportantLink,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -31,7 +31,8 @@ class UserStatusAction {
|
||||
suspend fun create(
|
||||
newStatus: String,
|
||||
signer: NostrSigner,
|
||||
): StatusEvent = StatusEvent.create(newStatus, "general", expiration = null, signer)
|
||||
type: String = StatusEvent.GENERAL,
|
||||
): StatusEvent = StatusEvent.create(newStatus, type, signer = signer)
|
||||
|
||||
suspend fun update(
|
||||
oldStatus: AddressableNote,
|
||||
|
||||
+15
@@ -24,6 +24,7 @@ import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.UserDependencies
|
||||
import com.vitorpamplona.quartz.nip40Expiration.expiration
|
||||
import com.vitorpamplona.quartz.nip40Expiration.isExpired
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -43,6 +44,9 @@ class UserStatusCache : UserDependencies {
|
||||
// if it's already there, quick exit
|
||||
if (statuses.value.contains(note) || note.event?.content.isNullOrBlank()) return
|
||||
|
||||
// don't add expired statuses
|
||||
if (note.event?.isExpired() == true) return
|
||||
|
||||
statuses.update {
|
||||
(it + note).sortedWith(sortModel).toImmutableList()
|
||||
}
|
||||
@@ -56,4 +60,15 @@ class UserStatusCache : UserDependencies {
|
||||
(it - deleteNote).toImmutableList()
|
||||
}
|
||||
}
|
||||
|
||||
fun removeExpired() {
|
||||
statuses.update { list ->
|
||||
val filtered = list.filter { it.event?.isExpired() != true }
|
||||
if (filtered.size != list.size) {
|
||||
filtered.toImmutableList()
|
||||
} else {
|
||||
list
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
-2
@@ -25,6 +25,10 @@ import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.firstTagValue
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrlTag
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
@@ -41,10 +45,18 @@ class StatusEvent(
|
||||
companion object {
|
||||
const val KIND = 30315
|
||||
|
||||
const val GENERAL = "general"
|
||||
const val MUSIC = "music"
|
||||
|
||||
suspend fun create(
|
||||
msg: String,
|
||||
type: String,
|
||||
expiration: Long?,
|
||||
type: String = GENERAL,
|
||||
expiration: Long? = null,
|
||||
url: String? = null,
|
||||
profileId: HexKey? = null,
|
||||
eventId: HexKey? = null,
|
||||
addressableId: String? = null,
|
||||
emojiTags: List<EmojiUrlTag>? = null,
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
): StatusEvent {
|
||||
@@ -52,6 +64,11 @@ class StatusEvent(
|
||||
|
||||
tags.add(arrayOf("d", type))
|
||||
expiration?.let { tags.add(arrayOf("expiration", it.toString())) }
|
||||
url?.let { tags.add(arrayOf("r", it)) }
|
||||
profileId?.let { tags.add(PTag.assemble(it, null)) }
|
||||
eventId?.let { tags.add(ETag.assemble(it, null, null)) }
|
||||
addressableId?.let { tags.add(ATag.assemble(it, null)) }
|
||||
emojiTags?.forEach { tags.add(it.toTagArray()) }
|
||||
|
||||
return signer.sign(createdAt, KIND, tags.toTypedArray(), msg)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user