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:
@@ -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()
|
||||||
|
|||||||
+8
-40
@@ -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) }
|
||||||
|
|||||||
+53
@@ -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,
|
||||||
|
|||||||
+7
@@ -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,
|
||||||
|
|||||||
+115
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -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
|
||||||
+1
-1
@@ -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
|
||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user