Finish the migration to separate channels

This commit is contained in:
Vitor Pamplona
2025-07-10 18:07:57 -04:00
parent 5daaa6364d
commit 62253d8601
11 changed files with 102 additions and 127 deletions

View File

@@ -760,14 +760,7 @@ class Account(
}
}
val isInChannel = note.channelHex()
val channelRelays =
if (isInChannel != null) {
val channel = LocalCache.checkGetOrCreateChannel(isInChannel)
channel?.relays() ?: emptySet()
} else {
emptySet()
}
val channelRelays = LocalCache.getAnyChannel(note)?.relays() ?: emptySet()
val replyRelays =
note.replyTo?.flatMapTo(mutableSetOf()) {

View File

@@ -1326,6 +1326,19 @@ object LocalCache : ILocalCache {
}
}
fun getAnyChannel(note: Note): Channel? = note.event?.let { getAnyChannel(it) }
fun getAnyChannel(noteEvent: Event): Channel? =
when (noteEvent) {
is ChannelCreateEvent -> getPublicChatChannelIfExists(noteEvent.id)
is ChannelMetadataEvent -> noteEvent.channelId()?.let { getPublicChatChannelIfExists(it) }
is ChannelMessageEvent -> noteEvent.channelId()?.let { getPublicChatChannelIfExists(it) }
is LiveActivitiesChatMessageEvent -> noteEvent.activityAddress()?.let { getLiveActivityChannelIfExists(it) }
is LiveActivitiesEvent -> getLiveActivityChannelIfExists(noteEvent.address())
is EphemeralChatEvent -> noteEvent.roomId()?.let { getEphemeralChatChannelIfExists(it) }
else -> null
}
@Suppress("DEPRECATION")
private fun deleteNote(deleteNote: Note) {
val deletedEvent = deleteNote.event
@@ -1349,11 +1362,7 @@ object LocalCache : ILocalCache {
masterNote.removeReport(deleteNote)
}
deleteNote.channelHex()?.let { getPublicChatChannelIfExists(it)?.removeNote(deleteNote) }
(deletedEvent as? LiveActivitiesChatMessageEvent)?.activity()?.let {
getPublicChatChannelIfExists(it.toTag())?.removeNote(deleteNote)
}
getAnyChannel(deleteNote)?.removeNote(deleteNote)
(deletedEvent as? TorrentCommentEvent)?.torrentIds()?.let {
getNoteIfExists(it)?.removeReply(deleteNote)

View File

@@ -29,7 +29,6 @@ import com.vitorpamplona.amethyst.service.replace
import com.vitorpamplona.amethyst.ui.note.toShortDisplay
import com.vitorpamplona.quartz.experimental.bounties.addedRewardValue
import com.vitorpamplona.quartz.experimental.bounties.hasAdditionalReward
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
import com.vitorpamplona.quartz.lightning.LnInvoiceUtil
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
@@ -51,8 +50,6 @@ import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
@@ -60,7 +57,6 @@ import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentRequestEvent
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceSuccessResponse
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
import com.vitorpamplona.quartz.nip56Reports.ReportType
@@ -214,25 +210,6 @@ open class Note(
open fun idDisplayNote() = idNote().toShortDisplay()
fun channelHex(): HexKey? =
if (
event is ChannelMessageEvent ||
event is ChannelMetadataEvent ||
event is ChannelCreateEvent ||
event is LiveActivitiesChatMessageEvent ||
event is LiveActivitiesEvent ||
event is EphemeralChatEvent
) {
(event as? ChannelMessageEvent)?.channelId()
?: (event as? ChannelMetadataEvent)?.channelId()
?: (event as? ChannelCreateEvent)?.id
?: (event as? LiveActivitiesChatMessageEvent)?.activity()?.toTag()
?: (event as? LiveActivitiesEvent)?.aTag()?.toTag()
?: (event as? EphemeralChatEvent)?.roomId()?.toKey()
} else {
null
}
open fun address(): Address? = null
open fun createdAt() = event?.createdAt

View File

@@ -35,21 +35,12 @@ fun potentialRelaysToFindAddress(note: AddressableNote): Set<NormalizedRelayUrl>
set.addAll(LocalCache.relayHints.hintsForAddress(note.idHex))
val isInChannel = note.channelHex()
if (isInChannel != null) {
LocalCache.checkGetOrCreateChannel(isInChannel)?.relays()?.forEach {
set.add(it)
}
}
LocalCache.getAnyChannel(note)?.relays()?.let { set.addAll(it) }
note.replyTo?.map { parentNote ->
set.addAll(parentNote.relays)
parentNote
.channelHex()
?.let { LocalCache.checkGetOrCreateChannel(it) }
?.relays()
?.forEach { set.add(it) }
LocalCache.getAnyChannel(parentNote)?.relays()?.let { set.addAll(it) }
parentNote.author?.inboxRelays()?.let { set.addAll(it) }
}
@@ -57,11 +48,7 @@ fun potentialRelaysToFindAddress(note: AddressableNote): Set<NormalizedRelayUrl>
note.replies.map { childNote ->
set.addAll(childNote.relays)
childNote
.channelHex()
?.let { LocalCache.checkGetOrCreateChannel(it) }
?.relays()
?.forEach { set.add(it) }
LocalCache.getAnyChannel(childNote)?.relays()?.let { set.addAll(it) }
childNote.author?.outboxRelays()?.let { set.addAll(it) }
}

View File

@@ -34,21 +34,12 @@ fun potentialRelaysToFindEvent(note: Note): Set<NormalizedRelayUrl> {
set.addAll(LocalCache.relayHints.hintsForEvent(note.idHex))
val isInChannel = note.channelHex()
if (isInChannel != null) {
LocalCache.checkGetOrCreateChannel(isInChannel)?.relays()?.forEach {
set.add(it)
}
}
LocalCache.getAnyChannel(note)?.relays()?.let { set.addAll(it) }
note.replyTo?.map { parentNote ->
set.addAll(parentNote.relays)
parentNote
.channelHex()
?.let { LocalCache.checkGetOrCreateChannel(it) }
?.relays()
?.forEach { set.add(it) }
LocalCache.getAnyChannel(parentNote)?.relays()?.let { set.addAll(it) }
parentNote.author?.inboxRelays()?.let { set.addAll(it) }
}
@@ -56,11 +47,7 @@ fun potentialRelaysToFindEvent(note: Note): Set<NormalizedRelayUrl> {
note.replies.map { childNote ->
set.addAll(childNote.relays)
childNote
.channelHex()
?.let { LocalCache.checkGetOrCreateChannel(it) }
?.relays()
?.forEach { set.add(it) }
LocalCache.getAnyChannel(childNote)?.relays()?.let { set.addAll(it) }
childNote.author?.outboxRelays()?.let { set.addAll(it) }
}

View File

@@ -272,16 +272,24 @@ fun AcceptableNote(
nav: INav,
) {
if (isQuotedNote || isBoostedNote) {
when (baseNote.event) {
is ChannelCreateEvent,
is ChannelMetadataEvent,
->
val noteEvent = baseNote.event
when (noteEvent) {
is ChannelCreateEvent ->
RenderPublicChatChannelHeader(
channelNote = baseNote,
channelId = noteEvent.id,
sendToChannel = true,
accountViewModel = accountViewModel,
nav = nav,
)
is ChannelMetadataEvent ->
noteEvent.channelId()?.let {
RenderPublicChatChannelHeader(
channelId = it,
sendToChannel = true,
accountViewModel = accountViewModel,
nav = nav,
)
}
is CommunityDefinitionEvent ->
(baseNote as? AddressableNote)?.let {
RenderCommunity(
@@ -311,16 +319,23 @@ fun AcceptableNote(
}
}
} else {
when (baseNote.event) {
is ChannelCreateEvent,
is ChannelMetadataEvent,
->
when (val noteEvent = baseNote.event) {
is ChannelCreateEvent ->
RenderPublicChatChannelHeader(
channelNote = baseNote,
channelId = noteEvent.id,
sendToChannel = true,
accountViewModel = accountViewModel,
nav = nav,
)
is ChannelMetadataEvent ->
noteEvent.channelId()?.let {
RenderPublicChatChannelHeader(
channelId = it,
sendToChannel = true,
accountViewModel = accountViewModel,
nav = nav,
)
}
is CommunityDefinitionEvent ->
(baseNote as? AddressableNote)?.let {
RenderCommunity(
@@ -1170,7 +1185,8 @@ private fun RenderAuthorImages(
nav: INav,
accountViewModel: AccountViewModel,
) {
if (baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent) {
val noteEvent = baseNote.event
if (noteEvent is RepostEvent || noteEvent is GenericRepostEvent) {
val baseRepost = baseNote.replyTo?.lastOrNull()
if (baseRepost != null) {
RepostNoteAuthorPicture(baseNote, baseRepost, accountViewModel, nav)
@@ -1181,8 +1197,8 @@ private fun RenderAuthorImages(
NoteAuthorPicture(baseNote, Size55dp, accountViewModel = accountViewModel, nav = nav)
}
if (baseNote.event is ChannelMessageEvent) {
val baseChannelHex = remember(baseNote) { baseNote.channelHex() }
if (noteEvent is ChannelMessageEvent) {
val baseChannelHex = noteEvent.channelId()
if (baseChannelHex != null) {
LoadPublicChatChannel(baseChannelHex, accountViewModel) { channel ->
ChannelNotePicture(

View File

@@ -32,6 +32,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
@@ -76,7 +77,7 @@ fun RenderPoll(
val replyingTo = noteEvent.replyingToAddressOrEvent()
if (replyingTo != null) {
val newNote = accountViewModel.getNoteIfExists(replyingTo)
if (newNote != null && newNote.channelHex() == null && newNote.event?.kind != CommunityDefinitionEvent.KIND) {
if (newNote != null && LocalCache.getAnyChannel(newNote) == null && newNote.event?.kind != CommunityDefinitionEvent.KIND) {
newNote
} else {
note.replyTo?.lastOrNull { it.event?.kind != CommunityDefinitionEvent.KIND }

View File

@@ -33,6 +33,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextOverflow
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
@@ -44,7 +45,6 @@ import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags
import com.vitorpamplona.quartz.nip01Core.tags.people.hasAnyTaggedUser
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
@@ -66,7 +66,7 @@ fun RenderTextEvent(
accountViewModel: AccountViewModel,
nav: INav,
) {
val noteEvent = note.event as? Event ?: return
val noteEvent = note.event ?: return
val showReply by
remember(note) {
@@ -82,7 +82,7 @@ fun RenderTextEvent(
val replyingTo = noteEvent.replyingToAddressOrEvent()
if (replyingTo != null) {
val newNote = accountViewModel.getNoteIfExists(replyingTo)
if (newNote != null && newNote.channelHex() == null && newNote.event?.kind != CommunityDefinitionEvent.KIND) {
if (newNote != null && LocalCache.getAnyChannel(newNote) == null && newNote.event?.kind != CommunityDefinitionEvent.KIND) {
newNote
} else {
note.replyTo?.lastOrNull { it.event?.kind != CommunityDefinitionEvent.KIND }

View File

@@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.navigation.INav
import com.vitorpamplona.amethyst.ui.note.LoadPublicChatChannel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -32,23 +31,22 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28P
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.StdPadding
import com.vitorpamplona.amethyst.ui.theme.innerPostModifier
import com.vitorpamplona.quartz.nip01Core.core.HexKey
@Composable
fun RenderPublicChatChannelHeader(
channelNote: Note,
channelId: HexKey,
sendToChannel: Boolean,
accountViewModel: AccountViewModel,
nav: INav,
) {
channelNote.channelHex()?.let {
PublicChatChannelHeader(
channelHex = it,
sendToChannel = sendToChannel,
modifier = MaterialTheme.colorScheme.innerPostModifier.padding(Size10dp),
accountViewModel = accountViewModel,
nav = nav,
)
}
PublicChatChannelHeader(
channelHex = channelId,
sendToChannel = sendToChannel,
modifier = MaterialTheme.colorScheme.innerPostModifier.padding(Size10dp),
accountViewModel = accountViewModel,
nav = nav,
)
}
@Composable

View File

@@ -102,7 +102,8 @@ class ChatroomListKnownFeedFilter(
newRelevantPublicMessages.forEach { newNotePair ->
var hasUpdated = false
oldList.forEach { oldNote ->
if (newNotePair.key == oldNote.channelHex()) {
val channelId = (oldNote.event as? ChannelMessageEvent)?.channelId()
if (newNotePair.key == channelId) {
hasUpdated = true
if ((newNotePair.value.createdAt() ?: 0) > (oldNote.createdAt() ?: 0)) {
myNewList = myNewList.replace(oldNote, newNotePair.value)
@@ -117,7 +118,8 @@ class ChatroomListKnownFeedFilter(
newRelevantEphemeralChats.forEach { newNotePair ->
var hasUpdated = false
oldList.forEach { oldNote ->
if (newNotePair.key.toKey() == oldNote.channelHex()) {
val noteEvent = (oldNote.event as? EphemeralChatEvent)?.roomId()
if (newNotePair.key == noteEvent) {
hasUpdated = true
if ((newNotePair.value.createdAt() ?: 0) > (oldNote.createdAt() ?: 0)) {
myNewList = myNewList.replace(oldNote, newNotePair.value)
@@ -171,17 +173,17 @@ class ChatroomListKnownFeedFilter(
val followingChannels = account.publicChatList.flowSet.value
val newRelevantPublicMessages = mutableMapOf<String, Note>()
newItems
.filter { it.event is ChannelMessageEvent }
.forEach { newNote ->
newNote.channelHex()?.let { channelHex ->
if (channelHex in followingChannels && account.isAcceptable(newNote)) {
val lastNote = newRelevantPublicMessages.get(channelHex)
val channelId = (newNote.event as? ChannelMessageEvent)?.channelId()
if (channelId != null) {
if (channelId in followingChannels && account.isAcceptable(newNote)) {
val lastNote = newRelevantPublicMessages.get(channelId)
if (lastNote != null) {
if ((newNote.createdAt() ?: 0) > (lastNote.createdAt() ?: 0)) {
newRelevantPublicMessages.put(channelHex, newNote)
newRelevantPublicMessages.put(channelId, newNote)
}
} else {
newRelevantPublicMessages.put(channelHex, newNote)
newRelevantPublicMessages.put(channelId, newNote)
}
}
}
@@ -198,7 +200,6 @@ class ChatroomListKnownFeedFilter(
newItems
.forEach { newNote ->
val noteEvent = newNote.event as? EphemeralChatEvent
if (noteEvent != null) {
val room = noteEvent.roomId()
if (room != null && room in followingEphemeralChats && account.isAcceptable(newNote)) {
@@ -225,27 +226,27 @@ class ChatroomListKnownFeedFilter(
val newRelevantPrivateMessages = mutableMapOf<ChatroomKey, Note>()
newItems
.filter { it.event is ChatroomKeyable }
.forEach { newNote ->
val roomKey = (newNote.event as? ChatroomKeyable)?.chatroomKey(me.pubkeyHex)
val room = account.userProfile().privateChatrooms[roomKey]
if (roomKey != null && room != null) {
if (
(
newNote.author?.pubkeyHex == me.pubkeyHex ||
room.senderIntersects(followingKeySet) ||
me.hasSentMessagesTo(roomKey)
) &&
!account.isAllHidden(roomKey.users)
) {
val lastNote = newRelevantPrivateMessages.get(roomKey)
if (lastNote != null) {
if ((newNote.createdAt() ?: 0) > (lastNote.createdAt() ?: 0)) {
if (roomKey != null) {
val room = account.userProfile().privateChatrooms[roomKey]
if (room != null) {
if (
(
newNote.author?.pubkeyHex == me.pubkeyHex ||
room.senderIntersects(followingKeySet) ||
me.hasSentMessagesTo(roomKey)
) &&
!account.isAllHidden(roomKey.users)
) {
val lastNote = newRelevantPrivateMessages.get(roomKey)
if (lastNote != null) {
if ((newNote.createdAt() ?: 0) > (lastNote.createdAt() ?: 0)) {
newRelevantPrivateMessages.put(roomKey, newNote)
}
} else {
newRelevantPrivateMessages.put(roomKey, newNote)
}
} else {
newRelevantPrivateMessages.put(roomKey, newNote)
}
}
}

View File

@@ -521,16 +521,22 @@ private fun FullBleedNoteCompose(
.padding(horizontal = 12.dp),
) {
Column {
if (
(noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) &&
baseNote.channelHex() != null
) {
if (noteEvent is ChannelCreateEvent) {
PublicChatChannelHeader(
channelHex = baseNote.channelHex()!!,
channelHex = noteEvent.id,
sendToChannel = true,
accountViewModel = accountViewModel,
nav = nav,
)
} else if (noteEvent is ChannelMetadataEvent) {
noteEvent.channelId()?.let {
PublicChatChannelHeader(
channelHex = it,
sendToChannel = true,
accountViewModel = accountViewModel,
nav = nav,
)
}
} else if (noteEvent is VideoEvent) {
VideoDisplay(baseNote, makeItShort = false, canPreview = true, backgroundColor = backgroundColor, ContentScale.FillWidth, accountViewModel = accountViewModel, nav = nav)
} else if (noteEvent is PictureEvent) {