feat(reads): integrate long-form into deck layout with zoom and reactions

- Wire Article, Editor, Drafts into DeckColumnType + DeckColumnContainer
- Add Drafts to sidebar nav, Add Column dialog, and Window menu
- Add article navigation (onNavigateToArticle) in SinglePaneLayout
- Add NoteActionsRow to ReadsScreen LongFormCards (zaps, reactions, replies)
- Add Cmd+/Cmd- zoom in ArticleReaderScreen via fontScale on RenderMarkdown
- Add kind 30023 to ProfileSubscription for long-form in user profiles
- Add fontScale param to RenderMarkdown using scaled LocalDensity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
nrobi144
2026-03-24 11:18:37 +02:00
parent 328fcf86d7
commit c75176891f
8 changed files with 280 additions and 10 deletions
@@ -24,8 +24,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.platform.UriHandler import androidx.compose.ui.platform.UriHandler
import androidx.compose.ui.unit.Density
import com.halilibo.richtext.commonmark.CommonMarkdownParseOptions import com.halilibo.richtext.commonmark.CommonMarkdownParseOptions
import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser import com.halilibo.richtext.commonmark.CommonmarkAstNodeParser
import com.halilibo.richtext.markdown.BasicMarkdown import com.halilibo.richtext.markdown.BasicMarkdown
@@ -39,6 +41,7 @@ fun RenderMarkdown(
content: String, content: String,
onLinkClick: (String) -> Unit, onLinkClick: (String) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
fontScale: Float = 1.0f,
) { ) {
val astNode = val astNode =
remember(content) { remember(content) {
@@ -57,7 +60,23 @@ fun RenderMarkdown(
} }
} }
CompositionLocalProvider(LocalUriHandler provides uriHandler) { val currentDensity = LocalDensity.current
val scaledDensity =
remember(fontScale, currentDensity) {
if (fontScale == 1.0f) {
currentDensity
} else {
Density(
density = currentDensity.density * fontScale,
fontScale = currentDensity.fontScale,
)
}
}
CompositionLocalProvider(
LocalUriHandler provides uriHandler,
LocalDensity provides scaledDensity,
) {
RichText( RichText(
modifier = modifier, modifier = modifier,
style = RichTextStyle(), style = RichTextStyle(),
@@ -379,6 +379,7 @@ fun main() {
Item("Messages", onClick = { deckState.addColumn(DeckColumnType.Messages) }) Item("Messages", onClick = { deckState.addColumn(DeckColumnType.Messages) })
Item("Search", onClick = { deckState.addColumn(DeckColumnType.Search) }) Item("Search", onClick = { deckState.addColumn(DeckColumnType.Search) })
Item("Reads", onClick = { deckState.addColumn(DeckColumnType.Reads) }) Item("Reads", onClick = { deckState.addColumn(DeckColumnType.Reads) })
Item("Drafts", onClick = { deckState.addColumn(DeckColumnType.Drafts) })
Item("Bookmarks", onClick = { deckState.addColumn(DeckColumnType.Bookmarks) }) Item("Bookmarks", onClick = { deckState.addColumn(DeckColumnType.Bookmarks) })
Item("Global Feed", onClick = { deckState.addColumn(DeckColumnType.GlobalFeed) }) Item("Global Feed", onClick = { deckState.addColumn(DeckColumnType.GlobalFeed) })
Item("Profile", onClick = { deckState.addColumn(DeckColumnType.MyProfile) }) Item("Profile", onClick = { deckState.addColumn(DeckColumnType.MyProfile) })
@@ -104,7 +104,11 @@ fun createUserPostsSubscription(
): SubscriptionConfig = ): SubscriptionConfig =
SubscriptionConfig( SubscriptionConfig(
subId = generateSubId("posts-${pubKeyHex.take(8)}"), subId = generateSubId("posts-${pubKeyHex.take(8)}"),
filters = listOf(FilterBuilders.textNotesFromAuthors(listOf(pubKeyHex), limit = limit)), filters =
listOf(
FilterBuilders.textNotesFromAuthors(listOf(pubKeyHex), limit = limit),
FilterBuilders.longFormFromAuthors(listOf(pubKeyHex), limit = 50),
),
relays = relays, relays = relays,
onEvent = onEvent, onEvent = onEvent,
onEose = onEose, onEose = onEose,
@@ -20,6 +20,7 @@
*/ */
package com.vitorpamplona.amethyst.desktop.ui package com.vitorpamplona.amethyst.desktop.ui
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -51,6 +52,13 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.isMetaPressed
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.compose.article.ArticleHeader import com.vitorpamplona.amethyst.commons.compose.article.ArticleHeader
import com.vitorpamplona.amethyst.commons.compose.article.TableOfContents import com.vitorpamplona.amethyst.commons.compose.article.TableOfContents
@@ -65,8 +73,17 @@ import com.vitorpamplona.amethyst.desktop.network.DesktopRelayConnectionManager
import com.vitorpamplona.amethyst.desktop.subscriptions.DesktopRelaySubscriptionsCoordinator import com.vitorpamplona.amethyst.desktop.subscriptions.DesktopRelaySubscriptionsCoordinator
import com.vitorpamplona.amethyst.desktop.subscriptions.FilterBuilders import com.vitorpamplona.amethyst.desktop.subscriptions.FilterBuilders
import com.vitorpamplona.amethyst.desktop.subscriptions.SubscriptionConfig import com.vitorpamplona.amethyst.desktop.subscriptions.SubscriptionConfig
import com.vitorpamplona.amethyst.desktop.subscriptions.createReactionsSubscription
import com.vitorpamplona.amethyst.desktop.subscriptions.createRepliesSubscription
import com.vitorpamplona.amethyst.desktop.subscriptions.createRepostsSubscription
import com.vitorpamplona.amethyst.desktop.subscriptions.createZapsSubscription
import com.vitorpamplona.amethyst.desktop.subscriptions.rememberSubscription import com.vitorpamplona.amethyst.desktop.subscriptions.rememberSubscription
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.BookmarkListEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import java.time.Instant import java.time.Instant
import java.time.ZoneId import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@@ -96,9 +113,12 @@ fun ArticleReaderScreen(
relayManager: DesktopRelayConnectionManager, relayManager: DesktopRelayConnectionManager,
localCache: DesktopLocalCache, localCache: DesktopLocalCache,
account: AccountState.LoggedIn?, account: AccountState.LoggedIn?,
nwcConnection: Nip47WalletConnect.Nip47URINorm? = null,
subscriptionsCoordinator: DesktopRelaySubscriptionsCoordinator? = null, subscriptionsCoordinator: DesktopRelaySubscriptionsCoordinator? = null,
onBack: () -> Unit, onBack: () -> Unit,
onNavigateToProfile: (String) -> Unit = {}, onNavigateToProfile: (String) -> Unit = {},
onNavigateToThread: (String) -> Unit = {},
onZapFeedback: (ZapFeedback) -> Unit = {},
) { ) {
val connectedRelays by relayManager.connectedRelays.collectAsState() val connectedRelays by relayManager.connectedRelays.collectAsState()
val relayStatuses by relayManager.relayStatuses.collectAsState() val relayStatuses by relayManager.relayStatuses.collectAsState()
@@ -113,6 +133,14 @@ fun ArticleReaderScreen(
var article by remember(addressTag) { mutableStateOf<LongTextNoteEvent?>(null) } var article by remember(addressTag) { mutableStateOf<LongTextNoteEvent?>(null) }
var eoseReceived by remember(addressTag) { mutableStateOf(false) } var eoseReceived by remember(addressTag) { mutableStateOf(false) }
// Zoom level for article text
var zoomLevel by remember { mutableStateOf(1.0f) }
val focusRequester =
remember {
androidx.compose.ui.focus
.FocusRequester()
}
// Active ToC entry tracking (placeholder — no scroll-position-based tracking yet) // Active ToC entry tracking (placeholder — no scroll-position-based tracking yet)
var activeTocIndex by remember { mutableStateOf<Int?>(null) } var activeTocIndex by remember { mutableStateOf<Int?>(null) }
@@ -165,6 +193,120 @@ fun ArticleReaderScreen(
) )
} }
// Interaction state
val articleEventId = article?.id
val eventIds = listOfNotNull(articleEventId)
var zapReceipts by remember { mutableStateOf<List<ZapReceipt>>(emptyList()) }
var reactionCount by remember { mutableStateOf(0) }
var replyCount by remember { mutableStateOf(0) }
var repostCount by remember { mutableStateOf(0) }
var bookmarkList by remember { mutableStateOf<BookmarkListEvent?>(null) }
var bookmarkedEventIds by remember { mutableStateOf<Set<String>>(emptySet()) }
// Subscribe to zaps
rememberSubscription(relayStatuses, eventIds, relayManager = relayManager) {
val configuredRelays = relayStatuses.keys
if (configuredRelays.isEmpty() || eventIds.isEmpty()) return@rememberSubscription null
createZapsSubscription(
relays = configuredRelays,
eventIds = eventIds,
onEvent = { event, _, _, _ ->
if (event is LnZapEvent) {
val receipt = event.toZapReceipt(localCache) ?: return@createZapsSubscription
if (zapReceipts.none { it.createdAt == receipt.createdAt && it.senderPubKey == receipt.senderPubKey }) {
zapReceipts = zapReceipts + receipt
}
}
},
)
}
// Subscribe to reactions
rememberSubscription(relayStatuses, eventIds, relayManager = relayManager) {
val configuredRelays = relayStatuses.keys
if (configuredRelays.isEmpty() || eventIds.isEmpty()) return@rememberSubscription null
val reactionIds = mutableSetOf<String>()
createReactionsSubscription(
relays = configuredRelays,
eventIds = eventIds,
onEvent = { event, _, _, _ ->
if (event is ReactionEvent && reactionIds.add(event.id)) {
reactionCount = reactionIds.size
}
},
)
}
// Subscribe to replies
rememberSubscription(relayStatuses, eventIds, relayManager = relayManager) {
val configuredRelays = relayStatuses.keys
if (configuredRelays.isEmpty() || eventIds.isEmpty()) return@rememberSubscription null
val replyIds = mutableSetOf<String>()
createRepliesSubscription(
relays = configuredRelays,
eventIds = eventIds,
onEvent = { event, _, _, _ ->
if (replyIds.add(event.id)) {
replyCount = replyIds.size
}
},
)
}
// Subscribe to reposts
rememberSubscription(relayStatuses, eventIds, relayManager = relayManager) {
val configuredRelays = relayStatuses.keys
if (configuredRelays.isEmpty() || eventIds.isEmpty()) return@rememberSubscription null
val repostIds = mutableSetOf<String>()
createRepostsSubscription(
relays = configuredRelays,
eventIds = eventIds,
onEvent = { event, _, _, _ ->
if (event is RepostEvent && repostIds.add(event.id)) {
repostCount = repostIds.size
}
},
)
}
// Subscribe to bookmark list
rememberSubscription(relayStatuses, account, relayManager = relayManager) {
val configuredRelays = relayStatuses.keys
if (configuredRelays.isNotEmpty() && account != null) {
SubscriptionConfig(
subId = "article-bookmarks-${account.pubKeyHex.take(8)}",
filters =
listOf(
FilterBuilders.byAuthors(
authors = listOf(account.pubKeyHex),
kinds = listOf(BookmarkListEvent.KIND),
limit = 1,
),
),
relays = configuredRelays,
onEvent = { event, _, _, _ ->
if (event is BookmarkListEvent) {
bookmarkList = event
bookmarkedEventIds =
event
.publicBookmarks()
.filterIsInstance<com.vitorpamplona.quartz.nip51Lists.bookmarkList.tags.EventBookmark>()
.map { it.eventId }
.toSet()
}
},
onEose = { _, _ -> },
)
} else {
null
}
}
// Derived data from article // Derived data from article
val title = article?.title() ?: "Untitled" val title = article?.title() ?: "Untitled"
val content = article?.content ?: "" val content = article?.content ?: ""
@@ -189,7 +331,43 @@ fun ArticleReaderScreen(
val authorName = authorUser?.toBestDisplayName() val authorName = authorUser?.toBestDisplayName()
val authorPicture = authorUser?.profilePicture() val authorPicture = authorUser?.profilePicture()
Column(modifier = Modifier.fillMaxSize()) { LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
Column(
modifier =
Modifier
.fillMaxSize()
.focusRequester(focusRequester)
.focusable()
.onPreviewKeyEvent { event ->
if (event.type == KeyEventType.KeyDown && event.isMetaPressed) {
when (event.key) {
Key.Equals -> {
zoomLevel = (zoomLevel + 0.1f).coerceAtMost(2.0f)
true
}
Key.Minus -> {
zoomLevel = (zoomLevel - 0.1f).coerceAtLeast(0.5f)
true
}
Key.Zero -> {
zoomLevel = 1.0f
true
}
else -> {
false
}
}
} else {
false
}
},
) {
// Top bar: back + bookmark placeholder // Top bar: back + bookmark placeholder
Row( Row(
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp), modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
@@ -210,6 +388,14 @@ fun ArticleReaderScreen(
style = MaterialTheme.typography.headlineMedium, style = MaterialTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.onBackground, color = MaterialTheme.colorScheme.onBackground,
) )
if (zoomLevel != 1.0f) {
Spacer(Modifier.width(8.dp))
Text(
"${(zoomLevel * 100).toInt()}%",
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
} }
} }
@@ -298,6 +484,7 @@ fun ArticleReaderScreen(
RenderMarkdown( RenderMarkdown(
content = content, content = content,
onLinkClick = onLinkClick, onLinkClick = onLinkClick,
fontScale = zoomLevel,
) )
Spacer(Modifier.height(32.dp)) Spacer(Modifier.height(32.dp))
@@ -321,6 +508,39 @@ fun ArticleReaderScreen(
HorizontalDivider(thickness = 1.dp) HorizontalDivider(thickness = 1.dp)
// Reaction actions
val art = article
if (art != null && account != null) {
Spacer(Modifier.height(16.dp))
NoteActionsRow(
event = art,
relayManager = relayManager,
localCache = localCache,
account = account,
onReplyClick = { onNavigateToThread(art.id) },
onZapFeedback = onZapFeedback,
zapCount = zapReceipts.size,
zapAmountSats = zapReceipts.sumOf { it.amountSats },
zapReceipts = zapReceipts,
reactionCount = reactionCount,
replyCount = replyCount,
repostCount = repostCount,
nwcConnection = nwcConnection,
isBookmarked = articleEventId in bookmarkedEventIds,
bookmarkList = bookmarkList,
onBookmarkChanged = { newList ->
bookmarkList = newList
bookmarkedEventIds =
newList
.publicBookmarks()
.filterIsInstance<com.vitorpamplona.quartz.nip51Lists.bookmarkList.tags.EventBookmark>()
.map { it.eventId }
.toSet()
},
modifier = Modifier.fillMaxWidth(),
)
}
Spacer(Modifier.height(48.dp)) Spacer(Modifier.height(48.dp))
} }
} }
@@ -40,6 +40,7 @@ import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChip
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -68,6 +69,7 @@ import com.vitorpamplona.amethyst.desktop.subscriptions.createLongFormFeedSubscr
import com.vitorpamplona.amethyst.desktop.subscriptions.rememberSubscription import com.vitorpamplona.amethyst.desktop.subscriptions.rememberSubscription
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect
import java.time.Instant import java.time.Instant
import java.time.ZoneId import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@@ -177,8 +179,11 @@ fun ReadsScreen(
relayManager: DesktopRelayConnectionManager, relayManager: DesktopRelayConnectionManager,
localCache: DesktopLocalCache, localCache: DesktopLocalCache,
account: AccountState.LoggedIn? = null, account: AccountState.LoggedIn? = null,
nwcConnection: Nip47WalletConnect.Nip47URINorm? = null,
onNavigateToProfile: (String) -> Unit = {}, onNavigateToProfile: (String) -> Unit = {},
onNavigateToArticle: (String) -> Unit = {}, onNavigateToArticle: (String) -> Unit = {},
onNavigateToThread: (String) -> Unit = {},
onZapFeedback: (ZapFeedback) -> Unit = {},
) { ) {
val connectedRelays by relayManager.connectedRelays.collectAsState() val connectedRelays by relayManager.connectedRelays.collectAsState()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -363,12 +368,27 @@ fun ReadsScreen(
verticalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp),
) { ) {
items(events, key = { it.id }) { event -> items(events, key = { it.id }) { event ->
Column {
LongFormCard( LongFormCard(
event = event, event = event,
localCache = localCache, localCache = localCache,
onAuthorClick = onNavigateToProfile, onAuthorClick = onNavigateToProfile,
onClick = { onNavigateToArticle(event.addressTag()) }, onClick = { onNavigateToArticle(event.addressTag()) },
) )
if (account != null) {
NoteActionsRow(
event = event,
relayManager = relayManager,
localCache = localCache,
account = account,
nwcConnection = nwcConnection,
onReplyClick = { onNavigateToThread(event.id) },
onZapFeedback = onZapFeedback,
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
)
}
}
HorizontalDivider(thickness = 1.dp)
} }
} }
} }
@@ -53,6 +53,7 @@ private val COLUMN_OPTIONS =
DeckColumnType.Messages, DeckColumnType.Messages,
DeckColumnType.Search, DeckColumnType.Search,
DeckColumnType.Reads, DeckColumnType.Reads,
DeckColumnType.Drafts,
DeckColumnType.Bookmarks, DeckColumnType.Bookmarks,
DeckColumnType.GlobalFeed, DeckColumnType.GlobalFeed,
DeckColumnType.MyProfile, DeckColumnType.MyProfile,
@@ -44,9 +44,9 @@ import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache
import com.vitorpamplona.amethyst.desktop.chess.ChessScreen import com.vitorpamplona.amethyst.desktop.chess.ChessScreen
import com.vitorpamplona.amethyst.desktop.model.DesktopIAccount import com.vitorpamplona.amethyst.desktop.model.DesktopIAccount
import com.vitorpamplona.amethyst.desktop.network.DesktopRelayConnectionManager import com.vitorpamplona.amethyst.desktop.network.DesktopRelayConnectionManager
import com.vitorpamplona.amethyst.desktop.service.drafts.DesktopDraftStore
import com.vitorpamplona.amethyst.desktop.subscriptions.DesktopRelaySubscriptionsCoordinator import com.vitorpamplona.amethyst.desktop.subscriptions.DesktopRelaySubscriptionsCoordinator
import com.vitorpamplona.amethyst.desktop.subscriptions.FeedMode import com.vitorpamplona.amethyst.desktop.subscriptions.FeedMode
import com.vitorpamplona.amethyst.desktop.service.drafts.DesktopDraftStore
import com.vitorpamplona.amethyst.desktop.ui.ArticleEditorScreen import com.vitorpamplona.amethyst.desktop.ui.ArticleEditorScreen
import com.vitorpamplona.amethyst.desktop.ui.ArticleReaderScreen import com.vitorpamplona.amethyst.desktop.ui.ArticleReaderScreen
import com.vitorpamplona.amethyst.desktop.ui.BookmarksScreen import com.vitorpamplona.amethyst.desktop.ui.BookmarksScreen
@@ -237,8 +237,11 @@ internal fun RootContent(
relayManager = relayManager, relayManager = relayManager,
localCache = localCache, localCache = localCache,
account = account, account = account,
nwcConnection = nwcConnection,
onNavigateToProfile = onNavigateToProfile, onNavigateToProfile = onNavigateToProfile,
onNavigateToArticle = onNavigateToArticle, onNavigateToArticle = onNavigateToArticle,
onNavigateToThread = onNavigateToThread,
onZapFeedback = onZapFeedback,
) )
} }
@@ -79,6 +79,7 @@ private val navItems =
listOf( listOf(
NavItem(DeckColumnType.HomeFeed, Icons.Default.Home, "Home"), NavItem(DeckColumnType.HomeFeed, Icons.Default.Home, "Home"),
NavItem(DeckColumnType.Reads, Icons.AutoMirrored.Filled.Article, "Reads"), NavItem(DeckColumnType.Reads, Icons.AutoMirrored.Filled.Article, "Reads"),
NavItem(DeckColumnType.Drafts, Icons.AutoMirrored.Filled.Article, "Drafts"),
NavItem(DeckColumnType.Search, Icons.Default.Search, "Search"), NavItem(DeckColumnType.Search, Icons.Default.Search, "Search"),
NavItem(DeckColumnType.Bookmarks, Icons.Default.Bookmark, "Bookmarks"), NavItem(DeckColumnType.Bookmarks, Icons.Default.Bookmark, "Bookmarks"),
NavItem(DeckColumnType.Messages, Icons.Default.Email, "Messages"), NavItem(DeckColumnType.Messages, Icons.Default.Email, "Messages"),
@@ -177,6 +178,7 @@ fun SinglePaneLayout(
onZapFeedback = onZapFeedback, onZapFeedback = onZapFeedback,
onNavigateToProfile = { navState.push(DesktopScreen.UserProfile(it)) }, onNavigateToProfile = { navState.push(DesktopScreen.UserProfile(it)) },
onNavigateToThread = { navState.push(DesktopScreen.Thread(it)) }, onNavigateToThread = { navState.push(DesktopScreen.Thread(it)) },
onNavigateToArticle = { navState.push(DesktopScreen.Article(it)) },
) )
if (currentOverlay != null) { if (currentOverlay != null) {
Surface( Surface(