feat: separate pinned notes into their own screen

Previously the Bookmarks screen bundled private, public, and pinned notes
into a single three-tab view. Pinned notes (NIP-51 kind 10001) are a
distinct concept from bookmarks (NIP-51 kind 10003) and deserve their
own destination.

- Remove the Pinned Notes tab from BookmarkListScreen (now two tabs)
- Add new PinnedNotesScreen and Route.PinnedNotes
- Move PinnedNotesFeedFilter/ViewModel to a new pinnednotes package
- List Pinned Notes as a sibling entry alongside Bookmarks and Old
  Bookmarks in ListOfBookmarkGroupsScreen so tapping Bookmarks in the
  drawer continues to surface all three lists
This commit is contained in:
Claude
2026-04-15 02:47:22 +00:00
parent de94b28fc2
commit d17b869dc2
9 changed files with 190 additions and 42 deletions
@@ -71,6 +71,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.list.metadat
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.membershipManagement.ArticleBookmarkListManagementScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.membershipManagement.ArticleBookmarkListManagementScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.membershipManagement.PostBookmarkListManagementScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.membershipManagement.PostBookmarkListManagementScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.old.OldBookmarkListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.old.OldBookmarkListScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.pinnednotes.PinnedNotesScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.AddMemberScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.AddMemberScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.CreateGroupScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.CreateGroupScreen
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.EditGroupInfoScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.EditGroupInfoScreen
@@ -258,6 +259,7 @@ fun BuildNavigation(
composableFromEnd<Route.OtsSettings> { OtsSettingsScreen(nav) } composableFromEnd<Route.OtsSettings> { OtsSettingsScreen(nav) }
composableFromEnd<Route.Bookmarks> { BookmarkListScreen(accountViewModel, nav) } composableFromEnd<Route.Bookmarks> { BookmarkListScreen(accountViewModel, nav) }
composableFromEnd<Route.OldBookmarks> { OldBookmarkListScreen(accountViewModel, nav) } composableFromEnd<Route.OldBookmarks> { OldBookmarkListScreen(accountViewModel, nav) }
composableFromEnd<Route.PinnedNotes> { PinnedNotesScreen(accountViewModel, nav) }
composableFromEnd<Route.WebBookmarks> { WebBookmarksScreen(accountViewModel, nav) } composableFromEnd<Route.WebBookmarks> { WebBookmarksScreen(accountViewModel, nav) }
composableFromEnd<Route.Drafts> { DraftListScreen(accountViewModel, nav) } composableFromEnd<Route.Drafts> { DraftListScreen(accountViewModel, nav) }
composableFromEnd<Route.Settings> { SettingsScreen(accountViewModel, nav) } composableFromEnd<Route.Settings> { SettingsScreen(accountViewModel, nav) }
@@ -82,6 +82,8 @@ sealed class Route {
@Serializable object OldBookmarks : Route() @Serializable object OldBookmarks : Route()
@Serializable object PinnedNotes : Route()
@Serializable object BookmarkGroups : Route() @Serializable object BookmarkGroups : Route()
@Serializable object ImportFollowsSelectUser : Route() @Serializable object ImportFollowsSelectUser : Route()
@@ -27,7 +27,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SecondaryScrollableTabRow import androidx.compose.material3.SecondaryTabRow
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -38,7 +38,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
@@ -50,7 +49,6 @@ import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.default.dal.BookmarkPrivateFeedViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.default.dal.BookmarkPrivateFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.default.dal.BookmarkPublicFeedViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.default.dal.BookmarkPublicFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.default.dal.PinnedNotesFeedViewModel
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -72,31 +70,18 @@ fun BookmarkListScreen(
factory = BookmarkPrivateFeedViewModel.Factory(accountViewModel.account), factory = BookmarkPrivateFeedViewModel.Factory(accountViewModel.account),
) )
val pinnedNotesFeedViewModel: PinnedNotesFeedViewModel =
viewModel(
key = "NostrPinnedNotesFeedViewModel",
factory = PinnedNotesFeedViewModel.Factory(accountViewModel.account),
)
val bookmarkState by accountViewModel.account.bookmarkState.bookmarks val bookmarkState by accountViewModel.account.bookmarkState.bookmarks
.collectAsStateWithLifecycle(null) .collectAsStateWithLifecycle(null)
val pinState by accountViewModel.account.pinState.pinnedNotesList
.collectAsStateWithLifecycle(null)
LaunchedEffect(bookmarkState) { LaunchedEffect(bookmarkState) {
publicFeedViewModel.invalidateData() publicFeedViewModel.invalidateData()
privateFeedViewModel.invalidateData() privateFeedViewModel.invalidateData()
} }
LaunchedEffect(pinState) { // Preload all bookmarked events so they don't load one-by-one when scrolling
pinnedNotesFeedViewModel.invalidateData() PreloadBookmarkEvents(bookmarkState, accountViewModel)
}
// Preload all bookmarked and pinned events so they don't load one-by-one when scrolling RenderBookmarkScreen(publicFeedViewModel, privateFeedViewModel, accountViewModel, nav)
PreloadBookmarkEvents(bookmarkState, pinState, accountViewModel)
RenderBookmarkScreen(publicFeedViewModel, privateFeedViewModel, pinnedNotesFeedViewModel, accountViewModel, nav)
} }
@Composable @Composable
@@ -104,11 +89,10 @@ fun BookmarkListScreen(
private fun RenderBookmarkScreen( private fun RenderBookmarkScreen(
publicFeedViewModel: BookmarkPublicFeedViewModel, publicFeedViewModel: BookmarkPublicFeedViewModel,
privateFeedViewModel: BookmarkPrivateFeedViewModel, privateFeedViewModel: BookmarkPrivateFeedViewModel,
pinnedNotesFeedViewModel: PinnedNotesFeedViewModel,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: INav, nav: INav,
) { ) {
val pagerState = rememberPagerState { 3 } val pagerState = rememberPagerState { 2 }
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
DisappearingScaffold( DisappearingScaffold(
@@ -116,11 +100,10 @@ private fun RenderBookmarkScreen(
topBar = { topBar = {
Column { Column {
TopBarWithBackButton(stringRes(id = R.string.bookmarks_title), nav::popBack) TopBarWithBackButton(stringRes(id = R.string.bookmarks_title), nav::popBack)
SecondaryScrollableTabRow( SecondaryTabRow(
containerColor = Color.Transparent, containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.onBackground, contentColor = MaterialTheme.colorScheme.onBackground,
selectedTabIndex = pagerState.currentPage, selectedTabIndex = pagerState.currentPage,
edgePadding = 8.dp,
modifier = TabRowHeight, modifier = TabRowHeight,
) { ) {
Tab( Tab(
@@ -133,11 +116,6 @@ private fun RenderBookmarkScreen(
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } }, onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
text = { Text(text = stringRes(R.string.public_bookmarks)) }, text = { Text(text = stringRes(R.string.public_bookmarks)) },
) )
Tab(
selected = pagerState.currentPage == 2,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(2) } },
text = { Text(text = stringRes(R.string.pinned_notes)) },
)
} }
} }
}, },
@@ -163,15 +141,6 @@ private fun RenderBookmarkScreen(
nav = nav, nav = nav,
) )
} }
2 -> {
RefresheableFeedView(
pinnedNotesFeedViewModel,
null,
accountViewModel = accountViewModel,
nav = nav,
)
}
} }
} }
} }
@@ -181,15 +150,14 @@ private fun RenderBookmarkScreen(
@Composable @Composable
private fun PreloadBookmarkEvents( private fun PreloadBookmarkEvents(
bookmarkState: com.vitorpamplona.amethyst.commons.model.nip51Lists.BookmarkListState.BookmarkList?, bookmarkState: com.vitorpamplona.amethyst.commons.model.nip51Lists.BookmarkListState.BookmarkList?,
pinState: List<com.vitorpamplona.amethyst.model.Note>?,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
) { ) {
val eventFinder = accountViewModel.dataSources().eventFinder val eventFinder = accountViewModel.dataSources().eventFinder
val account = accountViewModel.account val account = accountViewModel.account
val queries = val queries =
remember(bookmarkState, pinState) { remember(bookmarkState) {
val allNotes = (bookmarkState?.public.orEmpty() + bookmarkState?.private.orEmpty() + pinState.orEmpty()) val allNotes = bookmarkState?.public.orEmpty() + bookmarkState?.private.orEmpty()
allNotes allNotes
.filter { it.event == null } .filter { it.event == null }
.map { EventFinderQueryState(it, account) } .map { EventFinderQueryState(it, account) }
@@ -31,6 +31,7 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.BookmarkBorder import androidx.compose.material.icons.outlined.BookmarkBorder
import androidx.compose.material.icons.outlined.PushPin
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
@@ -44,6 +45,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.nip51Lists.BookmarkListState import com.vitorpamplona.amethyst.model.nip51Lists.BookmarkListState
import com.vitorpamplona.amethyst.model.nip51Lists.OldBookmarkListState import com.vitorpamplona.amethyst.model.nip51Lists.OldBookmarkListState
import com.vitorpamplona.amethyst.model.nip51Lists.PinListState
import com.vitorpamplona.amethyst.model.nip51Lists.labeledBookmarkLists.LabeledBookmarkList import com.vitorpamplona.amethyst.model.nip51Lists.labeledBookmarkLists.LabeledBookmarkList
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.BookmarkType import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.BookmarkType
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
@@ -57,9 +59,11 @@ import kotlinx.coroutines.flow.StateFlow
fun ListOfBookmarkGroupsFeedView( fun ListOfBookmarkGroupsFeedView(
defaultBookmarks: BookmarkListState, defaultBookmarks: BookmarkListState,
oldBookmarks: OldBookmarkListState, oldBookmarks: OldBookmarkListState,
pinnedNotes: PinListState,
groupListFeedSource: StateFlow<List<LabeledBookmarkList>>, groupListFeedSource: StateFlow<List<LabeledBookmarkList>>,
openDefaultBookmarks: () -> Unit, openDefaultBookmarks: () -> Unit,
openOldBookmarks: () -> Unit, openOldBookmarks: () -> Unit,
openPinnedNotes: () -> Unit,
onOpenItem: (String, BookmarkType) -> Unit, onOpenItem: (String, BookmarkType) -> Unit,
onRenameItem: (targetBookmarkGroup: LabeledBookmarkList) -> Unit, onRenameItem: (targetBookmarkGroup: LabeledBookmarkList) -> Unit,
onItemDescriptionChange: (bookmarkGroup: LabeledBookmarkList) -> Unit, onItemDescriptionChange: (bookmarkGroup: LabeledBookmarkList) -> Unit,
@@ -82,6 +86,11 @@ fun ListOfBookmarkGroupsFeedView(
HorizontalDivider(thickness = DividerThickness) HorizontalDivider(thickness = DividerThickness)
} }
item {
PinnedNotesList(pinnedNotes, openPinnedNotes)
HorizontalDivider(thickness = DividerThickness)
}
itemsIndexed( itemsIndexed(
bookmarkGroupFeedState, bookmarkGroupFeedState,
key = { _: Int, item: LabeledBookmarkList -> item.identifier }, key = { _: Int, item: LabeledBookmarkList -> item.identifier },
@@ -144,6 +153,50 @@ fun DefaultBookmarkList(
) )
} }
@Composable
fun PinnedNotesList(
pinnedNotes: PinListState,
openPinnedNotes: () -> Unit,
) {
val pinState by pinnedNotes.pinnedNotesList.collectAsStateWithLifecycle()
ListItem(
modifier = Modifier.clickable(onClick = openPinnedNotes),
headlineContent = {
Text(stringRes(R.string.pinned_notes), maxLines = 1, overflow = TextOverflow.Ellipsis)
},
supportingContent = {
Column(
modifier = Modifier.fillMaxWidth(),
) {
Text(
stringRes(R.string.pinned_notes_explainer),
overflow = TextOverflow.Ellipsis,
maxLines = 2,
)
}
},
leadingContent = {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
imageVector = Icons.Outlined.PushPin,
contentDescription = stringRes(R.string.bookmark_list_icon_label),
modifier = Size40Modifier,
)
Spacer(StdVertSpacer)
BookmarkMembershipStatusAndNumberDisplay(
modifier = Modifier.align(Alignment.CenterHorizontally),
postBookmarksSize = pinState.size,
articleBookmarksSize = 0,
)
}
},
)
}
@Composable @Composable
fun OldBookmarkList( fun OldBookmarkList(
oldBookmarks: OldBookmarkListState, oldBookmarks: OldBookmarkListState,
@@ -36,6 +36,7 @@ import androidx.compose.ui.Modifier
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.nip51Lists.BookmarkListState import com.vitorpamplona.amethyst.model.nip51Lists.BookmarkListState
import com.vitorpamplona.amethyst.model.nip51Lists.OldBookmarkListState import com.vitorpamplona.amethyst.model.nip51Lists.OldBookmarkListState
import com.vitorpamplona.amethyst.model.nip51Lists.PinListState
import com.vitorpamplona.amethyst.model.nip51Lists.labeledBookmarkLists.LabeledBookmarkList import com.vitorpamplona.amethyst.model.nip51Lists.labeledBookmarkLists.LabeledBookmarkList
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.Route
@@ -53,9 +54,11 @@ fun ListOfBookmarkGroupsScreen(
ListOfBookmarkGroupsFeed( ListOfBookmarkGroupsFeed(
defaultBookmarks = accountViewModel.account.bookmarkState, defaultBookmarks = accountViewModel.account.bookmarkState,
oldBookmarks = accountViewModel.account.oldBookmarkState, oldBookmarks = accountViewModel.account.oldBookmarkState,
pinnedNotes = accountViewModel.account.pinState,
listSource = accountViewModel.account.labeledBookmarkLists.listFeedFlow, listSource = accountViewModel.account.labeledBookmarkLists.listFeedFlow,
openDefaultBookmarks = { nav.nav(Route.Bookmarks) }, openDefaultBookmarks = { nav.nav(Route.Bookmarks) },
openOldBookmarks = { nav.nav(Route.OldBookmarks) }, openOldBookmarks = { nav.nav(Route.OldBookmarks) },
openPinnedNotes = { nav.nav(Route.PinnedNotes) },
addBookmarkGroup = { nav.nav(Route.BookmarkGroupMetadataEdit()) }, addBookmarkGroup = { nav.nav(Route.BookmarkGroupMetadataEdit()) },
openBookmarkGroup = { identifier, bookmarkType -> openBookmarkGroup = { identifier, bookmarkType ->
nav.nav(Route.BookmarkGroupView(identifier, bookmarkType)) nav.nav(Route.BookmarkGroupView(identifier, bookmarkType))
@@ -92,9 +95,11 @@ fun ListOfBookmarkGroupsScreen(
fun ListOfBookmarkGroupsFeed( fun ListOfBookmarkGroupsFeed(
defaultBookmarks: BookmarkListState, defaultBookmarks: BookmarkListState,
oldBookmarks: OldBookmarkListState, oldBookmarks: OldBookmarkListState,
pinnedNotes: PinListState,
listSource: StateFlow<List<LabeledBookmarkList>>, listSource: StateFlow<List<LabeledBookmarkList>>,
openDefaultBookmarks: () -> Unit, openDefaultBookmarks: () -> Unit,
openOldBookmarks: () -> Unit, openOldBookmarks: () -> Unit,
openPinnedNotes: () -> Unit,
addBookmarkGroup: () -> Unit, addBookmarkGroup: () -> Unit,
openBookmarkGroup: (identifier: String, bookmarkType: BookmarkType) -> Unit, openBookmarkGroup: (identifier: String, bookmarkType: BookmarkType) -> Unit,
renameBookmarkGroup: (bookmarkGroup: LabeledBookmarkList) -> Unit, renameBookmarkGroup: (bookmarkGroup: LabeledBookmarkList) -> Unit,
@@ -121,9 +126,11 @@ fun ListOfBookmarkGroupsFeed(
ListOfBookmarkGroupsFeedView( ListOfBookmarkGroupsFeedView(
defaultBookmarks = defaultBookmarks, defaultBookmarks = defaultBookmarks,
oldBookmarks = oldBookmarks, oldBookmarks = oldBookmarks,
pinnedNotes = pinnedNotes,
groupListFeedSource = listSource, groupListFeedSource = listSource,
openDefaultBookmarks = openDefaultBookmarks, openDefaultBookmarks = openDefaultBookmarks,
openOldBookmarks = openOldBookmarks, openOldBookmarks = openOldBookmarks,
openPinnedNotes = openPinnedNotes,
onOpenItem = openBookmarkGroup, onOpenItem = openBookmarkGroup,
onRenameItem = renameBookmarkGroup, onRenameItem = renameBookmarkGroup,
onItemDescriptionChange = changeBookmarkGroupDescription, onItemDescriptionChange = changeBookmarkGroupDescription,
@@ -0,0 +1,115 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.pinnednotes
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderQueryState
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.pinnednotes.dal.PinnedNotesFeedViewModel
import com.vitorpamplona.amethyst.ui.stringRes
@Composable
fun PinnedNotesScreen(
accountViewModel: AccountViewModel,
nav: INav,
) {
val pinnedNotesFeedViewModel: PinnedNotesFeedViewModel =
viewModel(
key = "NostrPinnedNotesFeedViewModel",
factory = PinnedNotesFeedViewModel.Factory(accountViewModel.account),
)
val pinState by accountViewModel.account.pinState.pinnedNotesList
.collectAsStateWithLifecycle(null)
LaunchedEffect(pinState) {
pinnedNotesFeedViewModel.invalidateData()
}
// Preload all pinned events so they don't load one-by-one when scrolling
PreloadPinnedEvents(pinState, accountViewModel)
RenderPinnedNotesScreen(pinnedNotesFeedViewModel, accountViewModel, nav)
}
@Composable
private fun RenderPinnedNotesScreen(
pinnedNotesFeedViewModel: PinnedNotesFeedViewModel,
accountViewModel: AccountViewModel,
nav: INav,
) {
DisappearingScaffold(
isInvertedLayout = false,
topBar = {
TopBarWithBackButton(stringRes(id = R.string.pinned_notes), nav::popBack)
},
accountViewModel = accountViewModel,
) {
Column(Modifier.padding(it).fillMaxHeight()) {
RefresheableFeedView(
pinnedNotesFeedViewModel,
null,
accountViewModel = accountViewModel,
nav = nav,
)
}
}
}
@Composable
private fun PreloadPinnedEvents(
pinState: List<Note>?,
accountViewModel: AccountViewModel,
) {
val eventFinder = accountViewModel.dataSources().eventFinder
val account = accountViewModel.account
val queries =
remember(pinState) {
pinState
.orEmpty()
.filter { it.event == null }
.map { EventFinderQueryState(it, account) }
}
DisposableEffect(queries) {
eventFinder.subscribe(queries)
onDispose {
eventFinder.unsubscribe(queries)
}
}
}
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.default.dal package com.vitorpamplona.amethyst.ui.screen.loggedIn.pinnednotes.dal
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.default.dal package com.vitorpamplona.amethyst.ui.screen.loggedIn.pinnednotes.dal
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
+1
View File
@@ -429,6 +429,7 @@
<string name="remove_from_public_bookmarks">Remove from Public Bookmarks</string> <string name="remove_from_public_bookmarks">Remove from Public Bookmarks</string>
<string name="pinned_notes">Pinned Notes</string> <string name="pinned_notes">Pinned Notes</string>
<string name="pinned_notes_explainer">Your pinned notes</string>
<string name="pin_to_profile">Pin to Profile</string> <string name="pin_to_profile">Pin to Profile</string>
<string name="unpin_from_profile">Unpin from Profile</string> <string name="unpin_from_profile">Unpin from Profile</string>