From 27e12a63d6ee4690477b4d51bdef312b7b19cecd Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 27 Jan 2023 15:50:17 -0300 Subject: [PATCH] Full integration between Channel Messages and Regular notes. --- .../vitorpamplona/amethyst/model/Account.kt | 7 +++++- .../vitorpamplona/amethyst/model/Channel.kt | 12 ++-------- .../amethyst/model/LocalCache.kt | 12 ++++++---- .../amethyst/service/NostrGlobalDataSource.kt | 6 +++-- .../service/NostrSingleEventDataSource.kt | 12 ++++++++-- .../amethyst/ui/actions/NewPostViewModel.kt | 7 +++++- .../amethyst/ui/navigation/Routes.kt | 4 ---- .../amethyst/ui/note/NoteCompose.kt | 6 ++--- .../amethyst/ui/note/ReplyInformation.kt | 24 +++++++++---------- .../ui/screen/loggedIn/ChannelScreen.kt | 2 +- 10 files changed, 52 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index d2fc850df..95abe0a83 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -196,12 +196,17 @@ class Account( LocalCache.consume(signedEvent) } - fun sendChannelMeesage(message: String, toChannel: String, replyingTo: Note? = null) { + fun sendChannelMeesage(message: String, toChannel: String, replyingTo: Note? = null, mentions: List?) { if (!isWriteable()) return + val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null } + val mentionsHex = mentions?.map { it.pubkeyHex } + val signedEvent = ChannelMessageEvent.create( message = message, channel = toChannel, + replyTos = repliesToHex, + mentions = mentionsHex, privateKey = loggedIn.privKey!! ) Client.send(signedEvent) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt index a8d9757b7..b93e5c772 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt @@ -1,13 +1,9 @@ package com.vitorpamplona.amethyst.model import androidx.lifecycle.LiveData -import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent -import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent import com.vitorpamplona.amethyst.ui.note.toShortenHex -import java.util.Collections import java.util.concurrent.ConcurrentHashMap -import nostr.postr.events.ContactListEvent class Channel(val id: ByteArray) { val idHex = id.toHexKey() @@ -21,12 +17,8 @@ class Channel(val id: ByteArray) { val notes = ConcurrentHashMap() @Synchronized - fun getOrCreateNote(idHex: String): Note { - return notes[idHex] ?: run { - val answer = Note(idHex) - notes.put(idHex, answer) - answer - } + fun addNote(note: Note) { + notes[note.idHex] = note } fun updateChannelInfo(creator: User, channelInfo: ChannelCreateEvent.ChannelData, updatedAt: Long) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 4cb2f0a13..5572dbfeb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -282,7 +282,8 @@ object LocalCache { if (oldChannel.creator == null || oldChannel.creator == author) { 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.loadEvent(event, author, emptyList(), mutableListOf()) @@ -303,7 +304,8 @@ object LocalCache { if (oldChannel.creator == null || oldChannel.creator == author) { 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.loadEvent(event, author, emptyList(), mutableListOf()) @@ -319,7 +321,8 @@ object LocalCache { 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. if (note.event != null) return @@ -328,7 +331,7 @@ object LocalCache { val mentions = Collections.synchronizedList(event.mentions.map { getOrCreateUser(decodePublicKey(it)) }) val replyTo = Collections.synchronizedList( event.replyTos - .map { channel.getOrCreateNote(it) } + .map { getOrCreateNote(it) } .filter { it.event !is ChannelCreateEvent } .toMutableList() ) @@ -373,6 +376,7 @@ object LocalCache { fun findNotesStartingWith(text: String): List { return notes.values.filter { (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.id.toNote().startsWith(text, true) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGlobalDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGlobalDataSource.kt index cc0536e1c..4660899ee 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGlobalDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGlobalDataSource.kt @@ -3,13 +3,14 @@ package com.vitorpamplona.amethyst.service import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent import nostr.postr.JsonFilter import nostr.postr.events.TextNoteEvent object NostrGlobalDataSource: NostrDataSource("GlobalFeed") { lateinit var account: Account fun createGlobalFilter() = JsonFilter( - kinds = listOf(TextNoteEvent.kind), + kinds = listOf(TextNoteEvent.kind, ChannelMessageEvent.kind), limit = 50 ) @@ -18,7 +19,8 @@ object NostrGlobalDataSource: NostrDataSource("GlobalFeed") { override fun feed() = LocalCache.notes.values .filter { account.isAcceptable(it) } .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 } .reversed() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt index e6458c18f..467faae68 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt @@ -2,6 +2,9 @@ package com.vitorpamplona.amethyst.service import com.vitorpamplona.amethyst.model.LocalCache 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.RepostEvent import java.util.Collections @@ -20,7 +23,9 @@ object NostrSingleEventDataSource: NostrDataSource("SingleEventFeed") { // downloads all the reactions to a given event. 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) ) } @@ -46,7 +51,10 @@ object NostrSingleEventDataSource: NostrDataSource("SingleEventFeed") { // downloads linked events to this event. 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() ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt index bbd50380a..dc4277e72 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt @@ -95,7 +95,12 @@ class NewPostViewModel: ViewModel() { }.joinToString(" ") }.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("") urlPreview = null } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt index be0cd61dc..4fe5a983b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt @@ -146,10 +146,6 @@ private fun messagesHasNewItems(cache: NotificationCache): Boolean { 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 }.isNotEmpty() } \ No newline at end of file diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 23503b4a9..1d0e0507a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -211,9 +211,9 @@ fun NoteCompose( if (eventContent != null) RichTextViewer(eventContent, note.event?.tags, navController) - if (note.event !is ChannelMessageEvent) { - ReactionsRow(note, accountViewModel) - } + //if (note.event !is ChannelMessageEvent) { + ReactionsRow(note, accountViewModel) + //} Divider( modifier = Modifier.padding(top = 10.dp), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt index aaa78692a..0685942ae 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt @@ -97,20 +97,20 @@ fun ReplyInformationChannel(replyTo: MutableList?, onChannelTagClick: (Channel) -> Unit ) { 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 (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( "replying to ", fontSize = 13.sp, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt index 04440db09..2492b2f49 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt @@ -125,7 +125,7 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun trailingIcon = { PostButton( onPost = { - account.sendChannelMeesage(newPost.value.text, channel.idHex) + account.sendChannelMeesage(newPost.value.text, channel.idHex, null, null) newPost.value = TextFieldValue("") }, newPost.value.text.isNotBlank(),