Full integration between Channel Messages and Regular notes.

This commit is contained in:
Vitor Pamplona
2023-01-27 15:50:17 -03:00
parent a934b42524
commit 27e12a63d6
10 changed files with 52 additions and 40 deletions

View File

@ -196,12 +196,17 @@ class Account(
LocalCache.consume(signedEvent) LocalCache.consume(signedEvent)
} }
fun sendChannelMeesage(message: String, toChannel: String, replyingTo: Note? = null) { fun sendChannelMeesage(message: String, toChannel: String, replyingTo: Note? = null, mentions: List<User>?) {
if (!isWriteable()) return if (!isWriteable()) return
val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
val mentionsHex = mentions?.map { it.pubkeyHex }
val signedEvent = ChannelMessageEvent.create( val signedEvent = ChannelMessageEvent.create(
message = message, message = message,
channel = toChannel, channel = toChannel,
replyTos = repliesToHex,
mentions = mentionsHex,
privateKey = loggedIn.privKey!! privateKey = loggedIn.privKey!!
) )
Client.send(signedEvent) Client.send(signedEvent)

View File

@ -1,13 +1,9 @@
package com.vitorpamplona.amethyst.model package com.vitorpamplona.amethyst.model
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
import com.vitorpamplona.amethyst.ui.note.toShortenHex import com.vitorpamplona.amethyst.ui.note.toShortenHex
import java.util.Collections
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import nostr.postr.events.ContactListEvent
class Channel(val id: ByteArray) { class Channel(val id: ByteArray) {
val idHex = id.toHexKey() val idHex = id.toHexKey()
@ -21,12 +17,8 @@ class Channel(val id: ByteArray) {
val notes = ConcurrentHashMap<HexKey, Note>() val notes = ConcurrentHashMap<HexKey, Note>()
@Synchronized @Synchronized
fun getOrCreateNote(idHex: String): Note { fun addNote(note: Note) {
return notes[idHex] ?: run { notes[note.idHex] = note
val answer = Note(idHex)
notes.put(idHex, answer)
answer
}
} }
fun updateChannelInfo(creator: User, channelInfo: ChannelCreateEvent.ChannelData, updatedAt: Long) { fun updateChannelInfo(creator: User, channelInfo: ChannelCreateEvent.ChannelData, updatedAt: Long) {

View File

@ -282,7 +282,8 @@ object LocalCache {
if (oldChannel.creator == null || oldChannel.creator == author) { if (oldChannel.creator == null || oldChannel.creator == author) {
oldChannel.updateChannelInfo(author, event.channelInfo, event.createdAt) oldChannel.updateChannelInfo(author, event.channelInfo, event.createdAt)
val note = oldChannel.getOrCreateNote(event.id.toHex()) val note = getOrCreateNote(event.id.toHex())
oldChannel.addNote(note)
note.channel = oldChannel note.channel = oldChannel
note.loadEvent(event, author, emptyList(), mutableListOf()) note.loadEvent(event, author, emptyList(), mutableListOf())
@ -303,7 +304,8 @@ object LocalCache {
if (oldChannel.creator == null || oldChannel.creator == author) { if (oldChannel.creator == null || oldChannel.creator == author) {
oldChannel.updateChannelInfo(author, event.channelInfo, event.createdAt) oldChannel.updateChannelInfo(author, event.channelInfo, event.createdAt)
val note = oldChannel.getOrCreateNote(event.id.toHex()) val note = getOrCreateNote(event.id.toHex())
oldChannel.addNote(note)
note.channel = oldChannel note.channel = oldChannel
note.loadEvent(event, author, emptyList(), mutableListOf()) note.loadEvent(event, author, emptyList(), mutableListOf())
@ -319,7 +321,8 @@ object LocalCache {
val channel = getOrCreateChannel(event.channel) val channel = getOrCreateChannel(event.channel)
val note = channel.getOrCreateNote(event.id.toHex()) val note = getOrCreateNote(event.id.toHex())
channel.addNote(note)
// Already processed this event. // Already processed this event.
if (note.event != null) return if (note.event != null) return
@ -328,7 +331,7 @@ object LocalCache {
val mentions = Collections.synchronizedList(event.mentions.map { getOrCreateUser(decodePublicKey(it)) }) val mentions = Collections.synchronizedList(event.mentions.map { getOrCreateUser(decodePublicKey(it)) })
val replyTo = Collections.synchronizedList( val replyTo = Collections.synchronizedList(
event.replyTos event.replyTos
.map { channel.getOrCreateNote(it) } .map { getOrCreateNote(it) }
.filter { it.event !is ChannelCreateEvent } .filter { it.event !is ChannelCreateEvent }
.toMutableList() .toMutableList()
) )
@ -373,6 +376,7 @@ object LocalCache {
fun findNotesStartingWith(text: String): List<Note> { fun findNotesStartingWith(text: String): List<Note> {
return notes.values.filter { return notes.values.filter {
(it.event is TextNoteEvent && it.event?.content?.contains(text, true) ?: false) (it.event is TextNoteEvent && it.event?.content?.contains(text, true) ?: false)
|| (it.event is ChannelMessageEvent && it.event?.content?.contains(text, true) ?: false)
|| it.idHex.startsWith(text, true) || it.idHex.startsWith(text, true)
|| it.id.toNote().startsWith(text, true) || it.id.toNote().startsWith(text, true)
} }

View File

@ -3,13 +3,14 @@ package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import nostr.postr.JsonFilter import nostr.postr.JsonFilter
import nostr.postr.events.TextNoteEvent import nostr.postr.events.TextNoteEvent
object NostrGlobalDataSource: NostrDataSource<Note>("GlobalFeed") { object NostrGlobalDataSource: NostrDataSource<Note>("GlobalFeed") {
lateinit var account: Account lateinit var account: Account
fun createGlobalFilter() = JsonFilter( fun createGlobalFilter() = JsonFilter(
kinds = listOf(TextNoteEvent.kind), kinds = listOf(TextNoteEvent.kind, ChannelMessageEvent.kind),
limit = 50 limit = 50
) )
@ -18,7 +19,8 @@ object NostrGlobalDataSource: NostrDataSource<Note>("GlobalFeed") {
override fun feed() = LocalCache.notes.values override fun feed() = LocalCache.notes.values
.filter { account.isAcceptable(it) } .filter { account.isAcceptable(it) }
.filter { .filter {
it.event is TextNoteEvent && (it.event as TextNoteEvent).replyTos.isEmpty() (it.event is TextNoteEvent && (it.event as TextNoteEvent).replyTos.isEmpty()) ||
(it.event is ChannelMessageEvent && (it.event as ChannelMessageEvent).replyTos.isEmpty())
} }
.sortedBy { it.event?.createdAt } .sortedBy { it.event?.createdAt }
.reversed() .reversed()

View File

@ -2,6 +2,9 @@ package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.ReactionEvent
import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.model.RepostEvent
import java.util.Collections import java.util.Collections
@ -20,7 +23,9 @@ object NostrSingleEventDataSource: NostrDataSource<Note>("SingleEventFeed") {
// downloads all the reactions to a given event. // downloads all the reactions to a given event.
return JsonFilter( return JsonFilter(
kinds = listOf(TextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind), kinds = listOf(
TextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind, ChannelMessageEvent.kind
),
tags = mapOf("e" to reactionsToWatch) tags = mapOf("e" to reactionsToWatch)
) )
} }
@ -46,7 +51,10 @@ object NostrSingleEventDataSource: NostrDataSource<Note>("SingleEventFeed") {
// downloads linked events to this event. // downloads linked events to this event.
return JsonFilter( return JsonFilter(
kinds = listOf(TextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind), kinds = listOf(
TextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind,
ChannelMessageEvent.kind, ChannelCreateEvent.kind, ChannelMetadataEvent.kind
),
ids = interestedEvents.toList() ids = interestedEvents.toList()
) )
} }

View File

@ -95,7 +95,12 @@ class NewPostViewModel: ViewModel() {
}.joinToString(" ") }.joinToString(" ")
}.joinToString("\n") }.joinToString("\n")
account?.sendPost(newMessage, replyTos, mentions) if (originalNote?.channel != null) {
account?.sendChannelMeesage(newMessage, originalNote!!.channel!!.idHex, originalNote!!, mentions)
} else {
account?.sendPost(newMessage, replyTos, mentions)
}
message = TextFieldValue("") message = TextFieldValue("")
urlPreview = null urlPreview = null
} }

View File

@ -146,10 +146,6 @@ private fun messagesHasNewItems(cache: NotificationCache): Boolean {
cache.load("Room/${it.author?.pubkeyHex}", context) cache.load("Room/${it.author?.pubkeyHex}", context)
} }
if (NostrChatroomListDataSource.account.isAcceptable(it) && it.event != null && it.event!!.createdAt > lastTime) {
println("${it.author?.toBestDisplayName()}")
}
NostrChatroomListDataSource.account.isAcceptable(it) && it.event != null && it.event!!.createdAt > lastTime NostrChatroomListDataSource.account.isAcceptable(it) && it.event != null && it.event!!.createdAt > lastTime
}.isNotEmpty() }.isNotEmpty()
} }

View File

@ -211,9 +211,9 @@ fun NoteCompose(
if (eventContent != null) if (eventContent != null)
RichTextViewer(eventContent, note.event?.tags, navController) RichTextViewer(eventContent, note.event?.tags, navController)
if (note.event !is ChannelMessageEvent) { //if (note.event !is ChannelMessageEvent) {
ReactionsRow(note, accountViewModel) ReactionsRow(note, accountViewModel)
} //}
Divider( Divider(
modifier = Modifier.padding(top = 10.dp), modifier = Modifier.padding(top = 10.dp),

View File

@ -97,20 +97,20 @@ fun ReplyInformationChannel(replyTo: MutableList<Note>?,
onChannelTagClick: (Channel) -> Unit onChannelTagClick: (Channel) -> Unit
) { ) {
FlowRow() { FlowRow() {
Text(
"in channel ",
fontSize = 13.sp,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
ClickableText(
AnnotatedString("${channel.info.name} "),
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(alpha = 0.52f), fontSize = 13.sp),
onClick = { onChannelTagClick(channel) }
)
if (mentions != null && mentions.isNotEmpty()) { if (mentions != null && mentions.isNotEmpty()) {
if (replyTo != null && replyTo.isNotEmpty()) { if (replyTo != null && replyTo.isNotEmpty()) {
Text(
"in channel ",
fontSize = 13.sp,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
ClickableText(
AnnotatedString("${channel.info.name} "),
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(alpha = 0.52f), fontSize = 13.sp),
onClick = { onChannelTagClick(channel) }
)
Text( Text(
"replying to ", "replying to ",
fontSize = 13.sp, fontSize = 13.sp,

View File

@ -125,7 +125,7 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun
trailingIcon = { trailingIcon = {
PostButton( PostButton(
onPost = { onPost = {
account.sendChannelMeesage(newPost.value.text, channel.idHex) account.sendChannelMeesage(newPost.value.text, channel.idHex, null, null)
newPost.value = TextFieldValue("") newPost.value = TextFieldValue("")
}, },
newPost.value.text.isNotBlank(), newPost.value.text.isNotBlank(),