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:
+20
-1
@@ -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) })
|
||||||
|
|||||||
+5
-1
@@ -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,
|
||||||
|
|||||||
+221
-1
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -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,
|
||||||
|
|||||||
+4
-1
@@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user