Introduce BookmarkGroupScreen, with it's associated ViewModel, and the different category-based views.
This commit is contained in:
@@ -57,6 +57,7 @@ import com.vitorpamplona.amethyst.ui.note.nip22Comments.ReplyCommentPostScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.display.BookmarkGroupScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.list.ListOfBookmarkGroupsScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarks.BookmarkListScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.ChatroomByAuthorScreen
|
||||
@@ -139,6 +140,7 @@ fun AppNavigation(
|
||||
composableFromBottomArgs<Route.FollowPackMetadataEdit> { FollowPackMetadataScreen(it.dTag, accountViewModel, nav) }
|
||||
|
||||
composableFromEnd<Route.BookmarkGroups> { ListOfBookmarkGroupsScreen(accountViewModel, nav) }
|
||||
composableFromEndArgs<Route.BookmarkGroupView> { BookmarkGroupScreen(it.dTag, it.bookmarkType, accountViewModel, nav) }
|
||||
|
||||
composableFromBottomArgs<Route.EditProfile> { NewUserMetadataScreen(nav, accountViewModel) }
|
||||
composable<Route.Search> { SearchScreen(accountViewModel, nav) }
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* 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.bookmarkgroups.display
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
|
||||
@Composable
|
||||
fun RenderArticleList(
|
||||
bookmarkGroupViewModel: BookmarkGroupViewModel,
|
||||
pagerState: PagerState,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val privateArticles = bookmarkGroupViewModel.privateArticles().collectAsStateWithLifecycle()
|
||||
val publicArticles = bookmarkGroupViewModel.publicArticles().collectAsStateWithLifecycle()
|
||||
|
||||
HorizontalPager(pagerState, modifier) { page ->
|
||||
when (page) {
|
||||
0 ->
|
||||
ArticleList(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
articles = publicArticles.value,
|
||||
isPrivate = false,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
1 ->
|
||||
ArticleList(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
articles = privateArticles.value,
|
||||
isPrivate = true,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ArticleList(
|
||||
modifier: Modifier = Modifier,
|
||||
articles: List<AddressableNote>,
|
||||
isPrivate: Boolean,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
contentPadding = FeedPadding,
|
||||
state = listState,
|
||||
) {
|
||||
itemsIndexed(articles, key = { _, item -> item.toNAddr() }) { _, item ->
|
||||
// TODO: Find a way to integrate bookmark group callbacks into the note below
|
||||
NoteCompose(
|
||||
baseNote = item,
|
||||
modifier = Modifier.animateContentSize(),
|
||||
quotesLeft = 3,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* 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.bookmarkgroups.display
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableBox
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.BookmarkType
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun BookmarkGroupScreen(
|
||||
bookmarkIdentifier: String,
|
||||
bookmarkType: BookmarkType,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val bookmarkGroupViewModel: BookmarkGroupViewModel =
|
||||
viewModel(
|
||||
factory = BookmarkGroupViewModel.Initializer(accountViewModel.account, bookmarkIdentifier),
|
||||
)
|
||||
|
||||
BookmarkGroupScreenView(
|
||||
bookmarkGroupViewModel,
|
||||
bookmarkType,
|
||||
broadcastBookmarkGroup = {
|
||||
accountViewModel.launchSigner {
|
||||
val groupNote = accountViewModel.account.labeledBookmarkLists.getLabeledBookmarkListNote(bookmarkIdentifier)
|
||||
groupNote?.let {
|
||||
accountViewModel.broadcast(it)
|
||||
}
|
||||
}
|
||||
},
|
||||
deleteBookmarkGroup = {
|
||||
accountViewModel.launchSigner {
|
||||
bookmarkGroupViewModel.deleteBookmarkGroup()
|
||||
}
|
||||
},
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BookmarkGroupScreenView(
|
||||
bookmarkGroupViewModel: BookmarkGroupViewModel,
|
||||
bookmarkType: BookmarkType,
|
||||
broadcastBookmarkGroup: () -> Unit,
|
||||
deleteBookmarkGroup: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val pagerState = rememberPagerState { 2 }
|
||||
val privateItemTypeLabel =
|
||||
when (bookmarkType) {
|
||||
BookmarkType.ArticleBookmark -> "Private Articles"
|
||||
BookmarkType.HashtagBookmark -> "Private Hashtags"
|
||||
BookmarkType.LinkBookmark -> "Private Links"
|
||||
BookmarkType.PostBookmark -> "Private Posts"
|
||||
}
|
||||
|
||||
val publicItemTypeLabel =
|
||||
when (bookmarkType) {
|
||||
BookmarkType.ArticleBookmark -> "Public Articles"
|
||||
BookmarkType.HashtagBookmark -> "Public Hashtags"
|
||||
BookmarkType.LinkBookmark -> "Public Links"
|
||||
BookmarkType.PostBookmark -> "Public Posts"
|
||||
}
|
||||
Scaffold(
|
||||
topBar = {
|
||||
Column {
|
||||
TopAppBar(
|
||||
title = {
|
||||
TitleAndDescription(bookmarkGroupViewModel)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(nav::popBack) {
|
||||
ArrowBackIcon()
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
BookmarkGroupActionsMenuButton(
|
||||
onBroadcastList = broadcastBookmarkGroup,
|
||||
onDeleteList = deleteBookmarkGroup,
|
||||
)
|
||||
},
|
||||
colors =
|
||||
TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
),
|
||||
)
|
||||
TabRow(
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||
selectedTabIndex = pagerState.currentPage,
|
||||
modifier = TabRowHeight,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
Tab(
|
||||
selected = pagerState.currentPage == 0,
|
||||
onClick = { scope.launch { pagerState.animateScrollToPage(0) } },
|
||||
text = { Text(text = publicItemTypeLabel) },
|
||||
)
|
||||
Tab(
|
||||
selected = pagerState.currentPage == 1,
|
||||
onClick = { scope.launch { pagerState.animateScrollToPage(1) } },
|
||||
text = { Text(text = privateItemTypeLabel) },
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
) { padding ->
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(
|
||||
top = padding.calculateTopPadding(),
|
||||
bottom = padding.calculateBottomPadding(),
|
||||
).consumeWindowInsets(padding)
|
||||
.imePadding(),
|
||||
) {
|
||||
when (bookmarkType) {
|
||||
BookmarkType.PostBookmark ->
|
||||
RenderPostList(
|
||||
bookmarkGroupViewModel,
|
||||
pagerState,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
BookmarkType.ArticleBookmark ->
|
||||
RenderArticleList(
|
||||
bookmarkGroupViewModel,
|
||||
pagerState,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
BookmarkType.HashtagBookmark -> RenderHashtagList(bookmarkGroupViewModel, pagerState)
|
||||
BookmarkType.LinkBookmark -> RenderLinksList(bookmarkGroupViewModel, pagerState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TitleAndDescription(viewModel: BookmarkGroupViewModel) {
|
||||
val selectedSetState = viewModel.selectedBookmarkGroupFlow.collectAsStateWithLifecycle()
|
||||
selectedSetState.value?.let { bookmarkGroup ->
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = bookmarkGroup.title,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
},
|
||||
supportingContent = {
|
||||
bookmarkGroup.description?.let { description ->
|
||||
Text(
|
||||
text = description,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BookmarkGroupActionsMenuButton(
|
||||
onBroadcastList: () -> Unit,
|
||||
onDeleteList: () -> Unit,
|
||||
) {
|
||||
val isActionListOpen = remember { mutableStateOf(false) }
|
||||
|
||||
ClickableBox(
|
||||
modifier =
|
||||
StdPadding
|
||||
.size(30.dp)
|
||||
.border(
|
||||
width = Dp.Hairline,
|
||||
color = ButtonDefaults.filledTonalButtonColors().containerColor,
|
||||
shape = ButtonBorder,
|
||||
).background(
|
||||
color = ButtonDefaults.filledTonalButtonColors().containerColor,
|
||||
shape = ButtonBorder,
|
||||
),
|
||||
onClick = { isActionListOpen.value = true },
|
||||
) {
|
||||
VerticalDotsIcon()
|
||||
BookmarkGroupActionsMenu(
|
||||
onCloseMenu = { isActionListOpen.value = false },
|
||||
isOpen = isActionListOpen.value,
|
||||
onBroadcastList = onBroadcastList,
|
||||
onDeleteList = onDeleteList,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BookmarkGroupActionsMenu(
|
||||
onCloseMenu: () -> Unit,
|
||||
isOpen: Boolean,
|
||||
onBroadcastList: () -> Unit,
|
||||
onDeleteList: () -> Unit,
|
||||
) {
|
||||
DropdownMenu(
|
||||
expanded = isOpen,
|
||||
onDismissRequest = onCloseMenu,
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text("Broadcast Bookmark Group")
|
||||
},
|
||||
onClick = {
|
||||
onBroadcastList()
|
||||
onCloseMenu()
|
||||
},
|
||||
)
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text("Delete Bookmark Group")
|
||||
},
|
||||
onClick = {
|
||||
onDeleteList()
|
||||
onCloseMenu()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* 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.bookmarkgroups.display
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.tags.AddressBookmark
|
||||
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.tags.BookmarkIdTag
|
||||
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.tags.EventBookmark
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
@Stable
|
||||
class BookmarkGroupViewModel(
|
||||
val account: Account,
|
||||
bookmarkGroupIdentifier: String,
|
||||
) : ViewModel() {
|
||||
val selectedBookmarkGroupFlow =
|
||||
account.labeledBookmarkLists
|
||||
.getLabeledBookmarkListFlow(bookmarkGroupIdentifier)
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(2500), null)
|
||||
|
||||
fun publicPosts() =
|
||||
selectedBookmarkGroupFlow
|
||||
.filterNotNull()
|
||||
.map { group ->
|
||||
group.publicBookmarks
|
||||
.filter { it is EventBookmark }
|
||||
.map { account.cache.getOrCreateNote((it as EventBookmark).eventId) }
|
||||
}.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
|
||||
|
||||
fun privatePosts() =
|
||||
selectedBookmarkGroupFlow
|
||||
.filterNotNull()
|
||||
.map { group ->
|
||||
group.privateBookmarks
|
||||
.filter { it is EventBookmark }
|
||||
.map { account.cache.getOrCreateNote((it as EventBookmark).eventId) }
|
||||
}.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
|
||||
|
||||
fun publicArticles() =
|
||||
selectedBookmarkGroupFlow
|
||||
.filterNotNull()
|
||||
.map { group ->
|
||||
group.publicBookmarks
|
||||
.filter { it is AddressBookmark }
|
||||
.map { account.cache.getOrCreateAddressableNote((it as AddressBookmark).address) }
|
||||
}.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
|
||||
|
||||
fun privateArticles() =
|
||||
selectedBookmarkGroupFlow
|
||||
.filterNotNull()
|
||||
.map { group ->
|
||||
group.privateBookmarks
|
||||
.filter { it is AddressBookmark }
|
||||
.map { account.cache.getOrCreateAddressableNote((it as AddressBookmark).address) }
|
||||
}.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
|
||||
|
||||
// TODO: Add implementations for Hashtag and Link bookmarks
|
||||
|
||||
suspend fun deleteBookmarkGroup() {
|
||||
selectedBookmarkGroupFlow.value?.let {
|
||||
account.labeledBookmarkLists.deleteBookmarkList(it, account)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun addBookmarkToGroup(
|
||||
bookmark: BookmarkIdTag,
|
||||
isPrivate: Boolean,
|
||||
account: Account,
|
||||
) {
|
||||
selectedBookmarkGroupFlow.value?.let {
|
||||
account.labeledBookmarkLists.addBookmarkToList(
|
||||
bookmark,
|
||||
it,
|
||||
isPrivate,
|
||||
account,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun removeBookmarkFromGroup(
|
||||
bookmark: BookmarkIdTag,
|
||||
isPrivate: Boolean,
|
||||
account: Account,
|
||||
) {
|
||||
selectedBookmarkGroupFlow.value?.let {
|
||||
account.labeledBookmarkLists.removeBookmarkFromList(
|
||||
bookmark,
|
||||
it,
|
||||
isPrivate,
|
||||
account,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Initializer(
|
||||
val account: Account,
|
||||
val bookmarkGroupIdentifier: String,
|
||||
) : ViewModelProvider.NewInstanceFactory() {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T = BookmarkGroupViewModel(account, bookmarkGroupIdentifier) as T
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.bookmarkgroups.display
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
fun RenderHashtagList(
|
||||
bookmarkGroupViewModel: BookmarkGroupViewModel,
|
||||
pagerState: PagerState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
HorizontalPager(pagerState, modifier) { page ->
|
||||
when (page) {
|
||||
0 -> HashtagsList(isPrivate = true)
|
||||
1 -> HashtagsList(isPrivate = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HashtagsList(
|
||||
modifier: Modifier = Modifier,
|
||||
// list: List<BookmarkIdTag>,
|
||||
isPrivate: Boolean,
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text("To be implemented")
|
||||
// Text("Number of ${if (isPrivate) "private" else "public"} hashtags: ${list.size}")
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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.bookmarkgroups.display
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
// TODO: Come back to work on this
|
||||
@Composable
|
||||
fun RenderLinksList(
|
||||
bookmarkGroupViewModel: BookmarkGroupViewModel,
|
||||
pagerState: PagerState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
HorizontalPager(pagerState, modifier) { page ->
|
||||
when (page) {
|
||||
0 -> LinksList(isPrivate = true)
|
||||
1 -> LinksList(isPrivate = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LinksList(
|
||||
// list: List<BookmarkIdTag>,
|
||||
isPrivate: Boolean,
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text("To be implemented")
|
||||
// Text("Number of ${if (isPrivate) "private" else "public"} links: ${list.size}")
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* 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.bookmarkgroups.display
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||
|
||||
@Composable
|
||||
fun RenderPostList(
|
||||
bookmarkGroupViewModel: BookmarkGroupViewModel,
|
||||
pagerState: PagerState,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val privatePosts = bookmarkGroupViewModel.privatePosts().collectAsStateWithLifecycle()
|
||||
val publicPosts = bookmarkGroupViewModel.publicPosts().collectAsStateWithLifecycle()
|
||||
|
||||
HorizontalPager(pagerState, modifier) { page ->
|
||||
when (page) {
|
||||
0 ->
|
||||
PostList(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
posts = publicPosts.value,
|
||||
isPrivate = false,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
1 ->
|
||||
PostList(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
posts = privatePosts.value,
|
||||
isPrivate = true,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PostList(
|
||||
modifier: Modifier = Modifier,
|
||||
posts: List<Note>,
|
||||
isPrivate: Boolean,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
contentPadding = FeedPadding,
|
||||
state = listState,
|
||||
) {
|
||||
itemsIndexed(posts, key = { _, item -> item.idHex }) { _, item ->
|
||||
// TODO: Find a way to integrate bookmark group callbacks into the note below
|
||||
NoteCompose(
|
||||
baseNote = item,
|
||||
modifier = Modifier.animateContentSize(),
|
||||
quotesLeft = 3,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user