Basic Support for live activities
This commit is contained in:
@@ -179,9 +179,14 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: PeopleListEvent) {
|
fun consume(event: PeopleListEvent) {
|
||||||
|
val version = getOrCreateNote(event.id)
|
||||||
val note = getOrCreateAddressableNote(event.address())
|
val note = getOrCreateAddressableNote(event.address())
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
|
if (version.event == null) {
|
||||||
|
version.loadEvent(event, author, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event?.id() == event.id()) return
|
if (note.event?.id() == event.id()) return
|
||||||
|
|
||||||
@@ -234,9 +239,14 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: LongTextNoteEvent, relay: Relay?) {
|
fun consume(event: LongTextNoteEvent, relay: Relay?) {
|
||||||
|
val version = getOrCreateNote(event.id)
|
||||||
val note = getOrCreateAddressableNote(event.address())
|
val note = getOrCreateAddressableNote(event.address())
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
|
if (version.event == null) {
|
||||||
|
version.loadEvent(event, author, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
if (relay != null) {
|
if (relay != null) {
|
||||||
author.addRelayBeingUsed(relay, event.createdAt)
|
author.addRelayBeingUsed(relay, event.createdAt)
|
||||||
note.addRelay(relay)
|
note.addRelay(relay)
|
||||||
@@ -299,10 +309,33 @@ object LocalCache {
|
|||||||
refreshObservers(note)
|
refreshObservers(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun consume(event: PinListEvent) {
|
private fun consume(event: LiveActivitiesEvent, relay: Relay?) {
|
||||||
|
val version = getOrCreateNote(event.id)
|
||||||
val note = getOrCreateAddressableNote(event.address())
|
val note = getOrCreateAddressableNote(event.address())
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
|
if (version.event == null) {
|
||||||
|
version.loadEvent(event, author, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (note.event?.id() == event.id()) return
|
||||||
|
|
||||||
|
if (event.createdAt > (note.createdAt() ?: 0)) {
|
||||||
|
note.loadEvent(event, author, emptyList())
|
||||||
|
|
||||||
|
refreshObservers(note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun consume(event: PinListEvent) {
|
||||||
|
val version = getOrCreateNote(event.id)
|
||||||
|
val note = getOrCreateAddressableNote(event.address())
|
||||||
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
|
if (version.event == null) {
|
||||||
|
version.loadEvent(event, author, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
if (note.event?.id() == event.id()) return
|
if (note.event?.id() == event.id()) return
|
||||||
|
|
||||||
if (event.createdAt > (note.createdAt() ?: 0)) {
|
if (event.createdAt > (note.createdAt() ?: 0)) {
|
||||||
@@ -313,9 +346,14 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun consume(event: RelaySetEvent) {
|
private fun consume(event: RelaySetEvent) {
|
||||||
|
val version = getOrCreateNote(event.id)
|
||||||
val note = getOrCreateAddressableNote(event.address())
|
val note = getOrCreateAddressableNote(event.address())
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
|
if (version.event == null) {
|
||||||
|
version.loadEvent(event, author, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
if (note.event?.id() == event.id()) return
|
if (note.event?.id() == event.id()) return
|
||||||
|
|
||||||
if (event.createdAt > (note.createdAt() ?: 0)) {
|
if (event.createdAt > (note.createdAt() ?: 0)) {
|
||||||
@@ -326,9 +364,14 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun consume(event: AudioTrackEvent) {
|
private fun consume(event: AudioTrackEvent) {
|
||||||
|
val version = getOrCreateNote(event.id)
|
||||||
val note = getOrCreateAddressableNote(event.address())
|
val note = getOrCreateAddressableNote(event.address())
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
|
if (version.event == null) {
|
||||||
|
version.loadEvent(event, author, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event?.id() == event.id()) return
|
if (note.event?.id() == event.id()) return
|
||||||
|
|
||||||
@@ -340,9 +383,14 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: BadgeDefinitionEvent) {
|
fun consume(event: BadgeDefinitionEvent) {
|
||||||
|
val version = getOrCreateNote(event.id)
|
||||||
val note = getOrCreateAddressableNote(event.address())
|
val note = getOrCreateAddressableNote(event.address())
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
|
if (version.event == null) {
|
||||||
|
version.loadEvent(event, author, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event?.id() == event.id()) return
|
if (note.event?.id() == event.id()) return
|
||||||
|
|
||||||
@@ -354,9 +402,14 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: BadgeProfilesEvent) {
|
fun consume(event: BadgeProfilesEvent) {
|
||||||
|
val version = getOrCreateNote(event.id)
|
||||||
val note = getOrCreateAddressableNote(event.address())
|
val note = getOrCreateAddressableNote(event.address())
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
|
if (version.event == null) {
|
||||||
|
version.loadEvent(event, author, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event?.id() == event.id()) return
|
if (note.event?.id() == event.id()) return
|
||||||
|
|
||||||
@@ -392,21 +445,33 @@ object LocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: AppDefinitionEvent) {
|
fun consume(event: AppDefinitionEvent) {
|
||||||
|
val version = getOrCreateNote(event.id)
|
||||||
val note = getOrCreateAddressableNote(event.address())
|
val note = getOrCreateAddressableNote(event.address())
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
|
if (version.event == null) {
|
||||||
|
version.loadEvent(event, author, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
|
if (event.createdAt > (note.createdAt() ?: 0)) {
|
||||||
note.loadEvent(event, author, emptyList())
|
note.loadEvent(event, author, emptyList())
|
||||||
|
|
||||||
refreshObservers(note)
|
refreshObservers(note)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun consume(event: AppRecommendationEvent) {
|
fun consume(event: AppRecommendationEvent) {
|
||||||
|
val version = getOrCreateNote(event.id)
|
||||||
val note = getOrCreateAddressableNote(event.address())
|
val note = getOrCreateAddressableNote(event.address())
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
|
|
||||||
|
if (version.event == null) {
|
||||||
|
version.loadEvent(event, author, emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event?.id() == event.id()) return
|
if (note.event?.id() == event.id()) return
|
||||||
|
|
||||||
@@ -1064,6 +1129,7 @@ object LocalCache {
|
|||||||
is FileStorageEvent -> consume(event, relay)
|
is FileStorageEvent -> consume(event, relay)
|
||||||
is FileStorageHeaderEvent -> consume(event, relay)
|
is FileStorageHeaderEvent -> consume(event, relay)
|
||||||
is HighlightEvent -> consume(event, relay)
|
is HighlightEvent -> consume(event, relay)
|
||||||
|
is LiveActivitiesEvent -> consume(event, relay)
|
||||||
is LnZapEvent -> {
|
is LnZapEvent -> {
|
||||||
event.zapRequest?.let {
|
event.zapRequest?.let {
|
||||||
verifyAndConsume(it, relay)
|
verifyAndConsume(it, relay)
|
||||||
|
|||||||
@@ -246,6 +246,7 @@ open class Event(
|
|||||||
FileStorageEvent.kind -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig)
|
FileStorageEvent.kind -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
FileStorageHeaderEvent.kind -> FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
FileStorageHeaderEvent.kind -> FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
HighlightEvent.kind -> HighlightEvent(id, pubKey, createdAt, tags, content, sig)
|
HighlightEvent.kind -> HighlightEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
LiveActivitiesEvent.kind -> LiveActivitiesEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
LnZapEvent.kind -> LnZapEvent(id, pubKey, createdAt, tags, content, sig)
|
LnZapEvent.kind -> LnZapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
LnZapPaymentRequestEvent.kind -> LnZapPaymentRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
LnZapPaymentRequestEvent.kind -> LnZapPaymentRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
LnZapPaymentResponseEvent.kind -> LnZapPaymentResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
LnZapPaymentResponseEvent.kind -> LnZapPaymentResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.vitorpamplona.amethyst.service.model
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
|
import nostr.postr.Utils
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class LiveActivitiesEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: List<List<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey
|
||||||
|
) : Event(id, pubKey, createdAt, kind, tags, content, sig), AddressableEvent {
|
||||||
|
|
||||||
|
override fun dTag() = tags.firstOrNull { it.size > 1 && it[0] == "d" }?.get(1) ?: ""
|
||||||
|
override fun address() = ATag(kind, pubKey, dTag(), null)
|
||||||
|
|
||||||
|
fun title() = tags.firstOrNull { it.size > 1 && it[0] == "title" }?.get(1)
|
||||||
|
fun summary() = tags.firstOrNull { it.size > 1 && it[0] == "summary" }?.get(1)
|
||||||
|
fun image() = tags.firstOrNull { it.size > 1 && it[0] == "image" }?.get(1)
|
||||||
|
fun streaming() = tags.firstOrNull { it.size > 1 && it[0] == "streaming" }?.get(1)
|
||||||
|
fun starts() = tags.firstOrNull { it.size > 1 && it[0] == "starts" }?.get(1)
|
||||||
|
fun ends() = tags.firstOrNull { it.size > 1 && it[0] == "ends" }?.get(1)
|
||||||
|
fun status() = tags.firstOrNull { it.size > 1 && it[0] == "status" }?.get(1)
|
||||||
|
fun currentParticipants() = tags.firstOrNull { it.size > 1 && it[0] == "current_participants" }?.get(1)
|
||||||
|
fun totalParticipants() = tags.firstOrNull { it.size > 1 && it[0] == "total_participants" }?.get(1)
|
||||||
|
|
||||||
|
fun participants() = tags.filter { it.size > 1 && it[0] == "p" }.map { Participant(it[1], it.getOrNull(2)) }
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val kind = 30311
|
||||||
|
|
||||||
|
fun create(
|
||||||
|
privateKey: ByteArray,
|
||||||
|
createdAt: Long = Date().time / 1000
|
||||||
|
): LiveActivitiesEvent {
|
||||||
|
val tags = mutableListOf<List<String>>()
|
||||||
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
|
val id = generateId(pubKey, createdAt, kind, tags, "")
|
||||||
|
val sig = Utils.sign(id, privateKey)
|
||||||
|
return LiveActivitiesEvent(id.toHexKey(), pubKey, createdAt, tags, "", sig.toHexKey())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -99,6 +99,7 @@ import com.vitorpamplona.amethyst.service.model.EventInterface
|
|||||||
import com.vitorpamplona.amethyst.service.model.FileHeaderEvent
|
import com.vitorpamplona.amethyst.service.model.FileHeaderEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.FileStorageHeaderEvent
|
import com.vitorpamplona.amethyst.service.model.FileStorageHeaderEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.HighlightEvent
|
import com.vitorpamplona.amethyst.service.model.HighlightEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.LiveActivitiesEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.Participant
|
import com.vitorpamplona.amethyst.service.model.Participant
|
||||||
import com.vitorpamplona.amethyst.service.model.PeopleListEvent
|
import com.vitorpamplona.amethyst.service.model.PeopleListEvent
|
||||||
@@ -675,6 +676,10 @@ private fun RenderNoteRow(
|
|||||||
RenderPinListEvent(baseNote, backgroundColor, accountViewModel, nav)
|
RenderPinListEvent(baseNote, backgroundColor, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is LiveActivitiesEvent -> {
|
||||||
|
RenderLiveActivityEvent(baseNote, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
|
||||||
is PrivateDmEvent -> {
|
is PrivateDmEvent -> {
|
||||||
RenderPrivateMessage(
|
RenderPrivateMessage(
|
||||||
baseNote,
|
baseNote,
|
||||||
@@ -2462,6 +2467,80 @@ fun AudioTrackHeader(noteEvent: AudioTrackEvent, accountViewModel: AccountViewMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RenderLiveActivityEvent(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
|
val noteEvent = baseNote.event as? LiveActivitiesEvent ?: return
|
||||||
|
|
||||||
|
val media = remember { noteEvent.streaming() }
|
||||||
|
val cover = remember { noteEvent.image() }
|
||||||
|
val subject = remember { noteEvent.title() }
|
||||||
|
val content = remember { noteEvent.summary() }
|
||||||
|
val participants = remember { noteEvent.participants() }
|
||||||
|
|
||||||
|
var participantUsers by remember { mutableStateOf<List<Pair<Participant, User>>>(emptyList()) }
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = participants) {
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
participantUsers = participants.mapNotNull { part ->
|
||||||
|
LocalCache.checkGetOrCreateUser(part.key)?.let { Pair(part, it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(modifier = Modifier.padding(top = 5.dp)) {
|
||||||
|
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Row() {
|
||||||
|
subject?.let {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 5.dp, bottom = 5.dp)) {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
maxLines = 3,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
participantUsers.forEach {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 5.dp, start = 10.dp, end = 10.dp)
|
||||||
|
.clickable {
|
||||||
|
nav("User/${it.second.pubkeyHex}")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
UserPicture(it.second, 25.dp, accountViewModel)
|
||||||
|
Spacer(Modifier.width(5.dp))
|
||||||
|
UsernameDisplay(it.second, Modifier.weight(1f))
|
||||||
|
Spacer(Modifier.width(5.dp))
|
||||||
|
it.first.role?.let {
|
||||||
|
Text(
|
||||||
|
text = it.capitalize(Locale.ROOT),
|
||||||
|
color = MaterialTheme.colors.placeholderText,
|
||||||
|
maxLines = 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
media?.let { media ->
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.padding(10.dp)
|
||||||
|
) {
|
||||||
|
VideoView(
|
||||||
|
videoUri = media,
|
||||||
|
description = subject
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LongFormHeader(noteEvent: LongTextNoteEvent, note: Note, accountViewModel: AccountViewModel) {
|
private fun LongFormHeader(noteEvent: LongTextNoteEvent, note: Note, accountViewModel: AccountViewModel) {
|
||||||
val image = remember(noteEvent) { noteEvent.image() }
|
val image = remember(noteEvent) { noteEvent.image() }
|
||||||
|
|||||||
Reference in New Issue
Block a user