From c0bcb638c0422a3360075a3042b4eaa2d0bf219c Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 2 Mar 2023 16:14:06 -0500 Subject: [PATCH] Bringing TextNoteEvent from NostrPostr to Amethyst's codebase and Restructuring Addressable Notes. --- .../vitorpamplona/amethyst/model/Account.kt | 6 +- .../com/vitorpamplona/amethyst/model/Hex.kt | 1 + .../amethyst/model/LocalCache.kt | 18 ++---- .../com/vitorpamplona/amethyst/model/Note.kt | 17 +++++- .../com/vitorpamplona/amethyst/model/User.kt | 22 ++++++++ .../service/NostrAccountDataSource.kt | 2 +- .../amethyst/service/NostrDataSource.kt | 6 +- .../amethyst/service/NostrGlobalDataSource.kt | 2 +- .../amethyst/service/NostrHomeDataSource.kt | 2 +- .../service/NostrSingleEventDataSource.kt | 31 ++++++++++- .../service/NostrUserProfileDataSource.kt | 2 +- .../service/model/LnZapRequestEvent.kt | 6 +- .../service/model/LongTextNoteEvent.kt | 6 ++ .../amethyst/service/model/ReactionEvent.kt | 7 ++- .../amethyst/service/model/ReportEvent.kt | 7 ++- .../amethyst/service/model/RepostEvent.kt | 7 ++- .../amethyst/service/model/TextNoteEvent.kt | 45 +++++++++++++++ .../amethyst/ui/actions/NewPostView.kt | 2 +- .../amethyst/ui/actions/NewPostViewModel.kt | 9 ++- .../amethyst/ui/components/ClickableRoute.kt | 6 +- .../amethyst/ui/components/RichTextViewer.kt | 55 ++++++++++++++----- .../amethyst/ui/dal/GlobalFeedFilter.kt | 2 +- .../ui/dal/HomeConversationsFeedFilter.kt | 2 +- .../ui/dal/HomeNewThreadFeedFilter.kt | 2 +- .../amethyst/ui/dal/NotificationFeedFilter.kt | 2 +- .../ui/dal/UserProfileNewThreadFeedFilter.kt | 2 +- .../amethyst/ui/navigation/Routes.kt | 2 +- .../amethyst/ui/note/BoostSetCompose.kt | 2 +- .../amethyst/ui/note/ChatroomCompose.kt | 4 +- .../amethyst/ui/note/LikeSetCompose.kt | 2 +- .../amethyst/ui/note/MultiSetCompose.kt | 2 +- .../amethyst/ui/note/NoteCompose.kt | 9 +-- .../amethyst/ui/note/ZapSetCompose.kt | 2 +- .../amethyst/ui/screen/ThreadFeedView.kt | 7 ++- .../ui/screen/loggedIn/ChannelScreen.kt | 2 +- 35 files changed, 231 insertions(+), 70 deletions(-) create mode 100644 app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt 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 50f1f4f00..1eb820b1d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -34,7 +34,7 @@ import nostr.postr.events.DeletionEvent import nostr.postr.events.Event import nostr.postr.events.MetadataEvent import nostr.postr.events.PrivateDmEvent -import nostr.postr.events.TextNoteEvent +import com.vitorpamplona.amethyst.service.model.TextNoteEvent import nostr.postr.toHex val DefaultChannels = setOf( @@ -290,18 +290,20 @@ class Account( val repliesToHex = replyTo?.map { it.idHex } val mentionsHex = mentions?.map { it.pubkeyHex } + val addressesHex = replyTo?.mapNotNull { it.address() } val signedEvent = TextNoteEvent.create( msg = message, replyTos = repliesToHex, mentions = mentionsHex, + addresses = addressesHex, privateKey = loggedIn.privKey!! ) Client.send(signedEvent) LocalCache.consume(signedEvent) } - fun sendChannelMeesage(message: String, toChannel: String, replyingTo: Note? = null, mentions: List?) { + fun sendChannelMessage(message: String, toChannel: String, replyingTo: Note? = null, mentions: List?) { if (!isWriteable()) return val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Hex.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Hex.kt index 429e53b6b..c8fb14180 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Hex.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Hex.kt @@ -74,6 +74,7 @@ fun parseDirtyWordForKey(mightBeAKey: String): DirtyKeyInfo? { return DirtyKeyInfo("note", keyB32.bechToBytes().toHexKey(), restOfWord) } + } catch (e: Exception) { e.printStackTrace() } 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 b8cc49b01..da84d2fa4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -37,7 +37,7 @@ import nostr.postr.events.Event import nostr.postr.events.MetadataEvent import nostr.postr.events.PrivateDmEvent import nostr.postr.events.RecommendRelayEvent -import nostr.postr.events.TextNoteEvent +import com.vitorpamplona.amethyst.service.model.TextNoteEvent import nostr.postr.toHex import nostr.postr.toNpub @@ -212,7 +212,7 @@ object LocalCache { //Log.d("TN", "New Note (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${note.event?.content?.take(100)} ${formattedDateTime(event.createdAt)}") // Prepares user's profile view. - author.addNote(note) + author.addLongFormNote(note) // Adds notifications to users. mentions.forEach { @@ -222,11 +222,6 @@ object LocalCache { it.author?.addTaggedPost(note) } - // Counts the replies - replyTo.forEach { - it.addReply(note) - } - refreshObservers() } @@ -492,7 +487,6 @@ object LocalCache { val note = getOrCreateNote(event.id.toHex()) oldChannel.addNote(note) - note.channel = oldChannel note.loadEvent(event, author, emptyList(), emptyList()) refreshObservers() @@ -514,7 +508,6 @@ object LocalCache { val note = getOrCreateNote(event.id.toHex()) oldChannel.addNote(note) - note.channel = oldChannel note.loadEvent(event, author, emptyList(), emptyList()) refreshObservers() @@ -553,7 +546,6 @@ object LocalCache { .mapNotNull { checkGetOrCreateNote(it) } .filter { it.event !is ChannelCreateEvent } - note.channel = channel note.loadEvent(event, author, mentions, replyTo) //Log.d("CM", "New Note (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${note.event?.content} ${formattedDateTime(event.createdAt)}") @@ -658,8 +650,9 @@ 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 TextNoteEvent && it.event?.content?.contains(text, true) ?: false) || (it.event is ChannelMessageEvent && it.event?.content?.contains(text, true) ?: false) + || (it.event is LongTextNoteEvent && it.event?.content?.contains(text, true) ?: false) || it.idHex.startsWith(text, true) || it.idNote().startsWith(text, true) } @@ -745,7 +738,7 @@ object LocalCache { fun pruneHiddenMessages(account: Account) { val toBeRemoved = account.hiddenUsers.map { - users[it]?.notes ?: emptySet() + (users[it]?.notes ?: emptySet()) + (users[it]?.longFormNotes?.values?.flatten() ?: emptySet()) }.flatten() account.hiddenUsers.forEach { @@ -754,6 +747,7 @@ object LocalCache { toBeRemoved.forEach { it.author?.removeNote(it) + it.author?.removeLongFormNote(it) // reverts the add it.mentions?.forEach { user -> diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index d464fe9e1..14db2aa01 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -2,7 +2,11 @@ package com.vitorpamplona.amethyst.model import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource +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.LnZapEvent +import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.relays.Relay @@ -23,6 +27,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import nostr.postr.events.Event +import nostr.postr.toHex val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]") @@ -49,14 +54,22 @@ class Note(val idHex: String) { var relays = setOf() private set - var channel: Channel? = null - var lastReactionsDownloadTime: Long? = null fun id() = Hex.decode(idHex) fun idNote() = id().toNote() fun idDisplayNote() = idNote().toShortenHex() + fun channel(): Channel? { + val channelHex = (event as? ChannelMessageEvent)?.channel ?: + (event as? ChannelMetadataEvent)?.channel ?: + (event as? ChannelCreateEvent)?.let { idHex } + + return channelHex?.let { LocalCache.checkGetOrCreateChannel(it) } + } + + fun address() = (event as? LongTextNoteEvent)?.address + fun loadEvent(event: Event, author: User, mentions: List, replyTo: List) { this.event = event this.author = author diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt index 67e656942..c201b76f5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.model import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource import com.vitorpamplona.amethyst.service.model.LnZapEvent +import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent import com.vitorpamplona.amethyst.service.model.ReportEvent import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.note.toShortenHex @@ -37,6 +38,8 @@ class User(val pubkeyHex: String) { var notes = setOf() private set + var longFormNotes = mapOf>() + private set var taggedPosts = setOf() private set @@ -142,8 +145,27 @@ class User(val pubkeyHex: String) { notes = notes - note } + fun addLongFormNote(note: Note) { + val address = (note.event as LongTextNoteEvent).address + + if (address in longFormNotes.keys) { + if (longFormNotes[address]?.contains(note) == false) + longFormNotes = longFormNotes + Pair(address, (longFormNotes[address] ?: emptySet()) + note) + } else { + longFormNotes = longFormNotes + Pair(address, setOf(note)) + // No need for Listener yet + } + } + + fun removeLongFormNote(note: Note) { + val address = (note.event as LongTextNoteEvent).address ?: return + + longFormNotes = longFormNotes - address + } + fun clearNotes() { notes = setOf() + longFormNotes = mapOf>() } fun addReport(note: Note) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt index 64cf24171..e7958ff7c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt @@ -11,7 +11,7 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter import nostr.postr.JsonFilter import nostr.postr.events.ContactListEvent import nostr.postr.events.MetadataEvent -import nostr.postr.events.TextNoteEvent +import com.vitorpamplona.amethyst.service.model.TextNoteEvent object NostrAccountDataSource: NostrDataSource("AccountData") { lateinit var account: Account diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt index 7d59dc02e..d9493450b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt @@ -32,7 +32,7 @@ import nostr.postr.events.Event import nostr.postr.events.MetadataEvent import nostr.postr.events.PrivateDmEvent import nostr.postr.events.RecommendRelayEvent -import nostr.postr.events.TextNoteEvent +import com.vitorpamplona.amethyst.service.model.TextNoteEvent abstract class NostrDataSource(val debugName: String) { private var subscriptions = mapOf() @@ -63,12 +63,14 @@ abstract class NostrDataSource(val debugName: String) { try { when (event) { is MetadataEvent -> LocalCache.consume(event) - is TextNoteEvent -> LocalCache.consume(event, relay) + //is TextNoteEvent -> LocalCache.consume(event, relay) overrides default TextNote is RecommendRelayEvent -> LocalCache.consume(event) is ContactListEvent -> LocalCache.consume(event) is PrivateDmEvent -> LocalCache.consume(event, relay) is DeletionEvent -> LocalCache.consume(event) else -> when (event.kind) { + TextNoteEvent.kind -> LocalCache.consume(TextNoteEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig), relay) + RepostEvent.kind -> { val repostEvent = RepostEvent(event.id, event.pubKey, event.createdAt, event.tags, event.content, event.sig) 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 a1ea5d004..d3ded698e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGlobalDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrGlobalDataSource.kt @@ -5,7 +5,7 @@ import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.TypedFilter import nostr.postr.JsonFilter -import nostr.postr.events.TextNoteEvent +import com.vitorpamplona.amethyst.service.model.TextNoteEvent object NostrGlobalDataSource: NostrDataSource("GlobalFeed") { fun createGlobalFilter() = TypedFilter( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt index 48894e5cf..2ff7a001a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt @@ -9,7 +9,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import nostr.postr.JsonFilter -import nostr.postr.events.TextNoteEvent +import com.vitorpamplona.amethyst.service.model.TextNoteEvent object NostrHomeDataSource: NostrDataSource("HomeFeed") { lateinit var account: Account 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 57f90eece..c35d2284c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleEventDataSource.kt @@ -14,11 +14,37 @@ import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.TypedFilter import java.util.Date import nostr.postr.JsonFilter -import nostr.postr.events.TextNoteEvent +import com.vitorpamplona.amethyst.service.model.TextNoteEvent object NostrSingleEventDataSource: NostrDataSource("SingleEventFeed") { private var eventsToWatch = setOf() + private fun createAddressFilter(): List? { + val addressesToWatch = eventsToWatch.map { LocalCache.getOrCreateNote(it) }.filter { it.address() != null } + + if (addressesToWatch.isEmpty()) { + return null + } + + val now = Date().time / 1000 + + return addressesToWatch.filter { + val lastTime = it.lastReactionsDownloadTime + lastTime == null || lastTime < (now - 10) + }.map { + TypedFilter( + types = FeedType.values().toSet(), + filter = JsonFilter( + kinds = listOf( + TextNoteEvent.kind, LongTextNoteEvent.kind, ReactionEvent.kind, RepostEvent.kind, ReportEvent.kind, LnZapEvent.kind, LnZapRequestEvent.kind + ), + tags = mapOf("a" to listOf(it.address()!!)), + since = it.lastReactionsDownloadTime + ) + ) + } + } + private fun createRepliesAndReactionsFilter(): List? { val reactionsToWatch = eventsToWatch.map { LocalCache.getOrCreateNote(it) } @@ -91,8 +117,9 @@ object NostrSingleEventDataSource: NostrDataSource("SingleEventFeed") { override fun updateChannelFilters() { val reactions = createRepliesAndReactionsFilter() val missing = createLoadEventsIfNotLoadedFilter() + val addresses = createAddressFilter() - singleEventChannel.typedFilters = listOfNotNull(reactions, missing).flatten().ifEmpty { null } + singleEventChannel.typedFilters = listOfNotNull(reactions, missing, addresses).flatten().ifEmpty { null } } fun add(eventId: String) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt index c399f5e3c..18fbafe2f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt @@ -9,7 +9,7 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter import nostr.postr.JsonFilter import nostr.postr.events.ContactListEvent import nostr.postr.events.MetadataEvent -import nostr.postr.events.TextNoteEvent +import com.vitorpamplona.amethyst.service.model.TextNoteEvent object NostrUserProfileDataSource: NostrDataSource("UserProfileFeed") { var user: User? = null diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt index c3d2c7568..9c6b57034 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt @@ -28,11 +28,15 @@ class LnZapRequestEvent ( fun create(originalNote: Event, relays: Set, privateKey: ByteArray, createdAt: Long = Date().time / 1000): LnZapRequestEvent { val content = "" val pubKey = Utils.pubkeyCreate(privateKey) - val tags = listOf( + var tags = listOf( listOf("e", originalNote.id.toHex()), listOf("p", originalNote.pubKey.toHex()), listOf("relays") + relays ) + if (originalNote is LongTextNoteEvent) { + tags = tags + listOf( listOf("a", originalNote.address) ) + } + val id = generateId(pubKey, createdAt, kind, tags, content) val sig = Utils.sign(id, privateKey) return LnZapRequestEvent(id, pubKey, createdAt, tags, content, sig) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt index 72c1d106f..1c2d67558 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LongTextNoteEvent.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.service.model +import com.vitorpamplona.amethyst.model.toHexKey import java.util.Date import nostr.postr.Utils import nostr.postr.events.Event @@ -20,11 +21,16 @@ class LongTextNoteEvent( @Transient val summary: String? @Transient val publishedAt: Long? @Transient val topics: List + @Transient val address: String + @Transient val dTag: String? init { replyTos = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) } mentions = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) } + dTag = tags.filter { it.firstOrNull() == "d" }.mapNotNull { it.getOrNull(1) }.firstOrNull() + + address = tags.filter { it.firstOrNull() == "a" }.mapNotNull { it.getOrNull(1) }.firstOrNull() ?: "$kind:${pubKey.toHexKey()}:$dTag" topics = tags.filter { it.firstOrNull() == "t" }.mapNotNull { it.getOrNull(1) } title = tags.filter { it.firstOrNull() == "title" }.mapNotNull { it.getOrNull(1) }.firstOrNull() image = tags.filter { it.firstOrNull() == "image" }.mapNotNull { it.getOrNull(1) }.firstOrNull() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ReactionEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ReactionEvent.kt index a5ac96524..264ac4990 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ReactionEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ReactionEvent.kt @@ -35,7 +35,12 @@ class ReactionEvent ( fun create(content: String, originalNote: Event, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReactionEvent { val pubKey = Utils.pubkeyCreate(privateKey) - val tags = listOf( listOf("e", originalNote.id.toHex()), listOf("p", originalNote.pubKey.toHex())) + + var tags = listOf( listOf("e", originalNote.id.toHex()), listOf("p", originalNote.pubKey.toHex())) + if (originalNote is LongTextNoteEvent) { + tags = tags + listOf( listOf("a", originalNote.address) ) + } + val id = generateId(pubKey, createdAt, kind, tags, content) val sig = Utils.sign(id, privateKey) return ReactionEvent(id, pubKey, createdAt, tags, content, sig) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ReportEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ReportEvent.kt index cbee5cf0b..1129ff579 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/ReportEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/ReportEvent.kt @@ -60,7 +60,12 @@ class ReportEvent ( val reportAuthorTag = listOf("p", reportedPost.pubKey.toHex(), type.name.toLowerCase()) val pubKey = Utils.pubkeyCreate(privateKey) - val tags:List> = listOf(reportPostTag, reportAuthorTag) + var tags:List> = listOf(reportPostTag, reportAuthorTag) + + if (reportedPost is LongTextNoteEvent) { + tags = tags + listOf( listOf("a", reportedPost.address) ) + } + val id = generateId(pubKey, createdAt, kind, tags, content) val sig = Utils.sign(id, privateKey) return ReportEvent(id, pubKey, createdAt, tags, content, sig) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/RepostEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/RepostEvent.kt index 3a1cc0a46..d0d405d5b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/RepostEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/RepostEvent.kt @@ -40,7 +40,12 @@ class RepostEvent ( val replyToAuthor = listOf("p", boostedPost.pubKey.toHex()) val pubKey = Utils.pubkeyCreate(privateKey) - val tags:List> = boostedPost.tags.plus(listOf(replyToPost, replyToAuthor)) + var tags:List> = boostedPost.tags.plus(listOf(replyToPost, replyToAuthor)) + + if (boostedPost is LongTextNoteEvent) { + tags = tags + listOf( listOf("a", boostedPost.address) ) + } + val id = generateId(pubKey, createdAt, kind, tags, content) val sig = Utils.sign(id, privateKey) return RepostEvent(id, pubKey, createdAt, tags, content, sig) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt new file mode 100644 index 000000000..e22c0418d --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/TextNoteEvent.kt @@ -0,0 +1,45 @@ +package com.vitorpamplona.amethyst.service.model + +import java.util.Date +import nostr.postr.Utils +import nostr.postr.events.Event + +class TextNoteEvent( + id: ByteArray, + pubKey: ByteArray, + createdAt: Long, + tags: List>, + content: String, + sig: ByteArray +): Event(id, pubKey, createdAt, kind, tags, content, sig) { + @Transient val replyTos: List + @Transient val mentions: List + @Transient val longFormAddress: List + + init { + longFormAddress = tags.filter { it.firstOrNull() == "a" }.mapNotNull { it.getOrNull(1) } + replyTos = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) } + mentions = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) } + } + + companion object { + const val kind = 1 + + fun create(msg: String, replyTos: List?, mentions: List?, addresses: List?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): TextNoteEvent { + val pubKey = Utils.pubkeyCreate(privateKey) + val tags = mutableListOf>() + replyTos?.forEach { + tags.add(listOf("e", it)) + } + mentions?.forEach { + tags.add(listOf("p", it)) + } + addresses?.forEach { + tags.add(listOf("a", it)) + } + val id = generateId(pubKey, createdAt, kind, tags, msg) + val sig = Utils.sign(id, privateKey) + return TextNoteEvent(id, pubKey, createdAt, tags, msg, sig) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 2d76b76a9..8d1ca4633 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -41,7 +41,7 @@ import com.vitorpamplona.amethyst.ui.navigation.UploadFromGallery import com.vitorpamplona.amethyst.ui.note.ReplyInformation import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine import kotlinx.coroutines.delay -import nostr.postr.events.TextNoteEvent +import com.vitorpamplona.amethyst.service.model.TextNoteEvent @OptIn(ExperimentalComposeUiApi::class) 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 37a9114a4..fcc03314f 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 @@ -46,7 +46,6 @@ class NewPostViewModel: ViewModel() { } else { this.mentions = currentMentions.plus(replyUser) } - } } @@ -68,12 +67,12 @@ class NewPostViewModel: ViewModel() { fun tagIndex(user: User): Int { // Postr Events assembles replies before mentions in the tag order - return (if (originalNote?.channel != null) 1 else 0) + (replyTos?.size ?: 0) + (mentions?.indexOf(user) ?: 0) + return (if (originalNote?.channel() != null) 1 else 0) + (replyTos?.size ?: 0) + (mentions?.indexOf(user) ?: 0) } fun tagIndex(note: Note): Int { // Postr Events assembles replies before mentions in the tag order - return (if (originalNote?.channel != null) 1 else 0) + (replyTos?.indexOf(note) ?: 0) + return (if (originalNote?.channel() != null) 1 else 0) + (replyTos?.indexOf(note) ?: 0) } fun sendPost() { @@ -108,8 +107,8 @@ class NewPostViewModel: ViewModel() { }.joinToString(" ") }.joinToString("\n") - if (originalNote?.channel != null) { - account?.sendChannelMeesage(newMessage, originalNote!!.channel!!.idHex, originalNote!!, mentions) + if (originalNote?.channel() != null) { + account?.sendChannelMessage(newMessage, originalNote!!.channel()!!.idHex, originalNote!!, mentions) } else { account?.sendPost(newMessage, replyTos, mentions) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt index 534548c84..6ef3abb29 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt @@ -42,10 +42,10 @@ fun ClickableRoute( onClick = { navController.navigate("Channel/${nip19.hex}") }, style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary) ) - } else if (note.channel != null) { + } else if (note.channel() != null) { ClickableText( - text = AnnotatedString("@${note.channel?.toBestDisplayName()} "), - onClick = { navController.navigate("Channel/${note.channel?.idHex}") }, + text = AnnotatedString("@${note.channel()?.toBestDisplayName()} "), + onClick = { navController.navigate("Channel/${note.channel()?.idHex}") }, style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary) ) } else { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt index 09620d90f..f4fa3dac3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/RichTextViewer.kt @@ -26,20 +26,20 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.text.Paragraph import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.google.accompanist.flowlayout.FlowRow import com.halilibo.richtext.markdown.Markdown import com.halilibo.richtext.markdown.MarkdownParseOptions -import com.halilibo.richtext.ui.RichText import com.halilibo.richtext.ui.RichTextStyle -import com.halilibo.richtext.ui.currentRichTextStyle import com.halilibo.richtext.ui.material.MaterialRichText import com.halilibo.richtext.ui.resolveDefaults -import com.halilibo.richtext.ui.string.RichTextString -import com.halilibo.richtext.ui.string.RichTextStringStyle import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.service.Nip19 @@ -81,20 +81,47 @@ fun RichTextViewer( navController: NavController, ) { + val myMarkDownStyle = RichTextStyle().resolveDefaults().copy( + codeBlockStyle = RichTextStyle().resolveDefaults().codeBlockStyle?.copy( + textStyle = TextStyle( + fontFamily = FontFamily.Monospace, + fontSize = 14.sp + ), + modifier = Modifier + .padding(0.dp) + .fillMaxWidth() + .clip(shape = RoundedCornerShape(15.dp)) + .border( + 1.dp, + MaterialTheme.colors.onSurface.copy(alpha = 0.12f), + RoundedCornerShape(15.dp) + ) + .background(MaterialTheme.colors.onSurface.copy(alpha = 0.05f).compositeOver(backgroundColor)) + ), + stringStyle = RichTextStyle().resolveDefaults().stringStyle?.copy( + linkStyle = SpanStyle( + textDecoration = TextDecoration.Underline, + color = MaterialTheme.colors.primary + ), + codeStyle = SpanStyle( + fontFamily = FontFamily.Monospace, + fontSize = 14.sp, + background = MaterialTheme.colors.onSurface.copy(alpha = 0.22f).compositeOver(backgroundColor) + ) + ) + ) + Column(modifier = modifier.animateContentSize()) { - if (content.startsWith("# ") || content.contains("##") || content.contains("```")) { - var richTextStyle by remember { mutableStateOf(RichTextStyle().resolveDefaults()) } + if ( content.startsWith("# ") + || content.contains("##") + || content.contains("**") + || content.contains("__") + || content.contains("```") + ) { MaterialRichText( - style = RichTextStyle().resolveDefaults().copy( - stringStyle = richTextStyle.stringStyle?.copy( - linkStyle = SpanStyle( - textDecoration = TextDecoration.Underline, - color = MaterialTheme.colors.primary - ) - ) - ), + style = myMarkDownStyle, ) { Markdown( content = content, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/GlobalFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/GlobalFeedFilter.kt index e02ee666b..010e3ef53 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/GlobalFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/GlobalFeedFilter.kt @@ -4,7 +4,7 @@ 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.events.TextNoteEvent +import com.vitorpamplona.amethyst.service.model.TextNoteEvent object GlobalFeedFilter: FeedFilter() { lateinit var account: Account diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeConversationsFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeConversationsFeedFilter.kt index 9a9efdbbb..1f96b3ee8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeConversationsFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeConversationsFeedFilter.kt @@ -4,7 +4,7 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.model.RepostEvent -import nostr.postr.events.TextNoteEvent +import com.vitorpamplona.amethyst.service.model.TextNoteEvent object HomeConversationsFeedFilter: FeedFilter() { lateinit var account: Account diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt index 275eb41d4..5276c2437 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/HomeNewThreadFeedFilter.kt @@ -5,7 +5,7 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent import com.vitorpamplona.amethyst.service.model.RepostEvent -import nostr.postr.events.TextNoteEvent +import com.vitorpamplona.amethyst.service.model.TextNoteEvent object HomeNewThreadFeedFilter: FeedFilter() { lateinit var account: Account diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt index 06a7e534d..80f94cd27 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/NotificationFeedFilter.kt @@ -7,7 +7,7 @@ import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.RepostEvent -import nostr.postr.events.TextNoteEvent +import com.vitorpamplona.amethyst.service.model.TextNoteEvent object NotificationFeedFilter: FeedFilter() { lateinit var account: Account diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileNewThreadFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileNewThreadFeedFilter.kt index 6407840f9..fd5316df0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileNewThreadFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileNewThreadFeedFilter.kt @@ -15,7 +15,7 @@ object UserProfileNewThreadFeedFilter: FeedFilter() { } override fun feed(): List { - return user?.notes + return user?.notes?.plus(user?.longFormNotes?.values?.flatten() ?: emptySet()) ?.filter { account?.isAcceptable(it) == true && it.isNewThread() } ?.sortedBy { it.event?.createdAt } ?.reversed() 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 12595420b..8da81cfa0 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 @@ -122,7 +122,7 @@ private fun messagesHasNewItems(account: Account, cache: NotificationCache, cont ChatroomListKnownFeedFilter.account = account val note = ChatroomListKnownFeedFilter.feed().firstOrNull { - it.event?.createdAt != null && it.channel == null && it.author != account.userProfile() + it.event?.createdAt != null && it.channel() == null && it.author != account.userProfile() } ?: return false val lastTime = cache.load("Room/${note.author?.pubkeyHex}", context) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BoostSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BoostSetCompose.kt index 98f613684..5462d4ef4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BoostSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BoostSetCompose.kt @@ -72,7 +72,7 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro launchSingleTop = true } } else { - note.channel?.let { + note.channel()?.let { navController.navigate("Channel/${it.idHex}") } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt index 204df89fe..91e97681b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt @@ -63,11 +63,11 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr if (note?.event == null) { BlankNote(Modifier) - } else if (note.channel != null) { + } else if (note.channel() != null) { val authorState by note.author!!.live().metadata.observeAsState() val author = authorState?.user - val channelState by note.channel!!.live.observeAsState() + val channelState by note.channel()!!.live.observeAsState() val channel = channelState?.channel val noteEvent = note.event diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/LikeSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/LikeSetCompose.kt index ac9f329f2..fdbec03b7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/LikeSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/LikeSetCompose.kt @@ -72,7 +72,7 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, modifier: Modifier = Modifier, isIn launchSingleTop = true } } else { - note.channel?.let { + note.channel()?.let { navController.navigate("Channel/${it.idHex}") } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt index ca121bb22..5cc2181d8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt @@ -78,7 +78,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, modifier: Modifier = Modifier, r launchSingleTop = true } } else { - note.channel?.let { + note.channel()?.let { navController.navigate("Channel/${it.idHex}") } } 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 ad043894f..c56c242f0 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 @@ -35,6 +35,7 @@ import com.google.accompanist.flowlayout.FlowRow import com.vitorpamplona.amethyst.NotificationCache import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.RoboHashCache +import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent @@ -51,7 +52,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.Following import kotlin.time.ExperimentalTime import nostr.postr.events.PrivateDmEvent -import nostr.postr.events.TextNoteEvent +import com.vitorpamplona.amethyst.service.model.TextNoteEvent @OptIn(ExperimentalFoundationApi::class) @Composable @@ -133,7 +134,7 @@ fun NoteCompose( launchSingleTop = true } } else { - note.channel?.let { + note.channel()?.let { navController.navigate("Channel/${it.idHex}") } } @@ -175,7 +176,7 @@ fun NoteCompose( } // boosted picture - val baseChannel = note.channel + val baseChannel = note.channel() if (noteEvent is ChannelMessageEvent && baseChannel != null) { val channelState by baseChannel.live.observeAsState() val channel = channelState?.channel @@ -293,7 +294,7 @@ fun NoteCompose( } else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || note.mentions != null)) { val sortedMentions = note.mentions?.toSet()?.sortedBy { account.userProfile().isFollowing(it) } - note.channel?.let { + note.channel()?.let { ReplyInformationChannel(note.replyTo, sortedMentions, it, navController) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapSetCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapSetCompose.kt index 1fd50f2c7..e1ecdcf77 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapSetCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapSetCompose.kt @@ -74,7 +74,7 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, modifier: Modifier = Modifier, isInner launchSingleTop = true } } else { - note.channel?.let { + note.channel()?.let { navController.navigate("Channel/${it.idHex}") } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt index ea386f65d..7f749f8d1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt @@ -268,7 +268,7 @@ fun NoteMaster(baseNote: Note, } if (noteEvent is LongTextNoteEvent) { - Row(modifier = Modifier.padding(horizontal = 12.dp)) { + Row(modifier = Modifier.padding(start = 12.dp, end = 12.dp, top = 10.dp)) { Column { noteEvent.image?.let { AsyncImage( @@ -295,7 +295,10 @@ fun NoteMaster(baseNote: Note, noteEvent.summary?.let { Text( - text = it + text = it, + modifier = Modifier + .fillMaxWidth() + .padding(top = 10.dp) ) } } 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 d756c98ec..55f9d219f 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 @@ -193,7 +193,7 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun trailingIcon = { PostButton( onPost = { - account.sendChannelMeesage(newPost.value.text, channel.idHex, replyTo.value, null) + account.sendChannelMessage(newPost.value.text, channel.idHex, replyTo.value, null) newPost.value = TextFieldValue("") replyTo.value = null feedViewModel.refresh() // Don't wait a full second before updating