Better rendering of Public Chats

This commit is contained in:
Vitor Pamplona
2026-05-22 18:25:07 -04:00
parent 3f91cb1689
commit 585b28163a
7 changed files with 133 additions and 54 deletions
@@ -82,7 +82,13 @@ fun SensitivityWarning(
accountViewModel: AccountViewModel,
content: @Composable () -> Unit,
) {
note.event?.let { SensitivityWarning(it, accountViewModel, content) }
val noteEvent = note.event
if (noteEvent == null) {
content()
} else {
SensitivityWarning(noteEvent, accountViewModel, content)
}
}
@Composable
@@ -21,23 +21,44 @@
package com.vitorpamplona.amethyst.ui.note
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.ui.note.elements.BannerImage
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.SimpleImageBorder
@Composable
fun DisplayAuthorBanner(
note: Note,
baseNote: Note,
accountViewModel: AccountViewModel,
modifier: Modifier = SimpleImageBorder,
) {
WatchAuthor(note, accountViewModel) {
val noteAuthor = baseNote.author
if (noteAuthor != null) {
BannerImage(
it,
noteAuthor,
modifier,
accountViewModel,
)
} else {
val authorState by observeNote(baseNote, accountViewModel)
CrossfadeIfEnabled(authorState.note.author, accountViewModel = accountViewModel) { author ->
if (author != null) {
BannerImage(
author,
modifier,
accountViewModel,
)
} else {
BannerImage(
null as String?,
modifier,
accountViewModel,
)
}
}
}
}
@@ -154,12 +154,11 @@ fun LoadPublicChatChannel(
accountViewModel: AccountViewModel,
content: @Composable (PublicChatChannel) -> Unit,
) {
val channel =
produceStateIfNotNull(accountViewModel.getPublicChatChannelIfExists(id), id) {
value = accountViewModel.checkGetOrCreatePublicChatChannel(id)
}
val channel by produceStateIfNotNull(accountViewModel.getPublicChatChannelIfExists(id), id) {
value = accountViewModel.checkGetOrCreatePublicChatChannel(id)
}
channel.value?.let { content(it) }
channel?.let { content(it) }
}
@Composable
@@ -119,38 +119,32 @@ fun BannerImage(
if (!banner.isNullOrBlank()) {
MyAsyncImage(
imageUrl = banner,
contentDescription =
stringRes(
R.string.preview_card_image_for,
banner,
),
contentDescription = stringRes(R.string.preview_card_image_for, banner),
contentScale = ContentScale.Crop,
mainImageModifier = Modifier,
loadedImageModifier = modifier,
accountViewModel = accountViewModel,
onLoadingBackground = {
Image(
painter = painterRes(R.drawable.profile_banner, 4),
contentDescription = stringRes(R.string.profile_banner),
contentScale = ContentScale.Crop,
modifier = modifier,
)
DefaultProfileBanner(modifier, 4)
},
onError = {
Image(
painter = painterRes(R.drawable.profile_banner, 4),
contentDescription = stringRes(R.string.profile_banner),
contentScale = ContentScale.Crop,
modifier = modifier,
)
DefaultProfileBanner(modifier, 4)
},
)
} else {
Image(
painter = painterRes(R.drawable.profile_banner, 5),
contentDescription = stringRes(R.string.profile_banner),
contentScale = ContentScale.Crop,
modifier = modifier,
)
DefaultProfileBanner(modifier, 5)
}
}
@Composable
fun DefaultProfileBanner(
modifier: Modifier,
sizeReference: Int,
) {
Image(
painter = painterRes(R.drawable.profile_banner, sizeReference),
contentDescription = stringRes(R.string.profile_banner),
contentScale = ContentScale.Crop,
modifier = modifier,
)
}
@@ -68,7 +68,6 @@ import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.grayText
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@@ -81,8 +80,6 @@ fun RenderPublicChatChannelThumb(
accountViewModel: AccountViewModel,
nav: INav,
) {
val noteEvent = baseNote.event as? ChannelCreateEvent ?: return
LoadPublicChatChannel(baseNote.idHex, accountViewModel) {
RenderPublicChatChannelThumb(baseNote = baseNote, channel = it, accountViewModel, nav)
}
@@ -96,7 +93,7 @@ fun RenderPublicChatChannelThumb(
nav: INav,
) {
val channelUpdates by observeChannel(channel, accountViewModel)
val publicChat = channelUpdates?.channel as PublicChatChannel
val publicChat = (channelUpdates?.channel as? PublicChatChannel) ?: channel
val name = remember(channelUpdates) { publicChat.toBestDisplayName() }
val description = remember(channelUpdates) { publicChat.summary()?.ifBlank { null } }
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.publicChats
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@@ -45,15 +46,20 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.layouts.rememberFeedContentPadding
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport
import com.vitorpamplona.amethyst.ui.note.ClickableNote
import com.vitorpamplona.amethyst.ui.note.LongPressToQuickAction
import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent
import com.vitorpamplona.amethyst.ui.note.calculateBackgroundColor
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.ChannelCardCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip28Chats.RenderPublicChatChannelThumb
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
import com.vitorpamplona.amethyst.ui.theme.StdPadding
@Composable
fun PublicChatsFeedLoaded(
@@ -66,12 +72,8 @@ fun PublicChatsFeedLoaded(
val followedSet by accountViewModel.account.publicChatList.flowSet
.collectAsStateWithLifecycle()
val pinned =
remember(followedSet) {
followedSet.mapNotNull { idHex ->
LocalCache.getNoteIfExists(idHex)?.takeIf { it.event is ChannelCreateEvent }
}
}
val pinned by accountViewModel.account.publicChatList.flowSetNote
.collectAsStateWithLifecycle()
val unpinned =
remember(items.list, followedSet) {
@@ -91,7 +93,6 @@ fun PublicChatsFeedLoaded(
items(
pinned,
key = { item -> "pinned-" + item.idHex },
contentType = { item -> item.event?.kind ?: -1 },
) { item ->
PublicChatRow(item, pinned = true, accountViewModel, nav)
}
@@ -105,7 +106,6 @@ fun PublicChatsFeedLoaded(
itemsIndexed(
unpinned,
key = { _, item -> item.idHex },
contentType = { _, item -> item.event?.kind ?: -1 },
) { _, item ->
PublicChatRow(item, pinned = false, accountViewModel, nav)
}
@@ -114,20 +114,34 @@ fun PublicChatsFeedLoaded(
@Composable
private fun LazyItemScope.PublicChatRow(
item: Note,
baseNote: Note,
pinned: Boolean,
accountViewModel: AccountViewModel,
nav: INav,
) {
val modifier = Modifier.fillMaxWidth()
Box(Modifier.fillMaxWidth().animateItem()) {
ChannelCardCompose(
baseNote = item,
routeForLastRead = "PublicChatsFeed",
modifier = Modifier.fillMaxWidth(),
forceEventKind = ChannelCreateEvent.KIND,
WatchNoteEvent(
baseNote = baseNote,
accountViewModel = accountViewModel,
nav = nav,
onBlank = {
RenderChannel(baseNote, modifier, accountViewModel, nav)
},
onNoteEventFound = {
CheckHiddenFeedWatchBlockAndReport(
note = baseNote,
modifier = modifier,
ignoreAllBlocksAndReports = false,
showHiddenWarning = false,
accountViewModel = accountViewModel,
nav = nav,
) { _ ->
RenderChannel(baseNote, modifier, accountViewModel, nav)
}
},
)
if (pinned) {
PinBadge(
modifier =
@@ -143,6 +157,40 @@ private fun LazyItemScope.PublicChatRow(
)
}
@Composable
private fun RenderChannel(
baseNote: Note,
modifier: Modifier,
accountViewModel: AccountViewModel,
nav: INav,
) {
LongPressToQuickAction(baseNote, accountViewModel, nav) { showPopup ->
ClickableNote(
baseNote = baseNote,
backgroundColor =
calculateBackgroundColor(
createdAt = baseNote.createdAt(),
routeForLastRead = "PublicChatsFeed",
parentBackgroundColor = null,
accountViewModel = accountViewModel,
),
modifier = modifier,
accountViewModel = accountViewModel,
showPopup = showPopup,
nav = nav,
) {
Column(StdPadding) {
SensitivityWarning(
note = baseNote,
accountViewModel = accountViewModel,
) {
RenderPublicChatChannelThumb(baseNote, accountViewModel, nav)
}
}
}
}
}
@Composable
private fun PinBadge(modifier: Modifier = Modifier) {
Box(
@@ -36,6 +36,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
@@ -96,6 +97,19 @@ class PublicChatListState(
emptySet(),
)
val flowSetNote =
flowSet
.map {
it.mapNotNull {
cache.checkGetOrCreateNote(it)
}
}.flowOn(Dispatchers.IO)
.stateIn(
scope,
SharingStarted.Eagerly,
emptyList(),
)
suspend fun follow(channel: PublicChatChannel): ChannelListEvent {
val publicChatList = getChannelList()