feat: split polls feed into Open and Closed tabs
Adds tabbed navigation to the polls screen, separating polls into "Open" (active/no end date) and "Closed" (ended) tabs. Uses the same tab pattern as HomeScreen with HorizontalPager and SecondaryTabRow. https://claude.ai/code/session_0168P3u8aMmn4HnSBDXR7yv7
This commit is contained in:
@@ -56,6 +56,8 @@ object ScrollStateKeys {
|
|||||||
const val DISCOVER_CHATS = "DiscoverChatsFeed"
|
const val DISCOVER_CHATS = "DiscoverChatsFeed"
|
||||||
|
|
||||||
const val POLLS_SCREEN = "PollsFeed"
|
const val POLLS_SCREEN = "PollsFeed"
|
||||||
|
const val POLLS_OPEN = "PollsOpenFeed"
|
||||||
|
const val POLLS_CLOSED = "PollsClosedFeed"
|
||||||
const val PICTURES_SCREEN = "PicturesFeed"
|
const val PICTURES_SCREEN = "PicturesFeed"
|
||||||
const val SHORTS_SCREEN = "ShortsFeed"
|
const val SHORTS_SCREEN = "ShortsFeed"
|
||||||
const val LONGS_SCREEN = "LongsFeed"
|
const val LONGS_SCREEN = "LongsFeed"
|
||||||
@@ -68,6 +70,7 @@ object ScrollStateKeys {
|
|||||||
object PagerStateKeys {
|
object PagerStateKeys {
|
||||||
const val HOME_SCREEN = "PagerHome"
|
const val HOME_SCREEN = "PagerHome"
|
||||||
const val DISCOVER_SCREEN = "PagerDiscover"
|
const val DISCOVER_SCREEN = "PagerDiscover"
|
||||||
|
const val POLLS_SCREEN = "PagerPolls"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+8
@@ -46,6 +46,8 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationS
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.OpenPollsState
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.OpenPollsState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.dal.NotificationFeedFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.dal.NotificationFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.pictures.dal.PictureFeedFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.pictures.dal.PictureFeedFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.dal.ClosedPollsFeedFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.dal.OpenPollsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.dal.PollsFeedFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.dal.PollsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.shorts.dal.ShortsFeedFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.shorts.dal.ShortsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.dal.VideoFeedFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.dal.VideoFeedFilter
|
||||||
@@ -74,6 +76,8 @@ class AccountFeedContentStates(
|
|||||||
val discoverPublicChats = FeedContentState(DiscoverChatFeedFilter(account), scope, LocalCache)
|
val discoverPublicChats = FeedContentState(DiscoverChatFeedFilter(account), scope, LocalCache)
|
||||||
|
|
||||||
val pollsFeed = FeedContentState(PollsFeedFilter(account), scope, LocalCache)
|
val pollsFeed = FeedContentState(PollsFeedFilter(account), scope, LocalCache)
|
||||||
|
val openPollsFeed = FeedContentState(OpenPollsFeedFilter(account), scope, LocalCache)
|
||||||
|
val closedPollsFeed = FeedContentState(ClosedPollsFeedFilter(account), scope, LocalCache)
|
||||||
|
|
||||||
val picturesFeed = FeedContentState(PictureFeedFilter(account), scope, LocalCache)
|
val picturesFeed = FeedContentState(PictureFeedFilter(account), scope, LocalCache)
|
||||||
val shortsFeed = FeedContentState(ShortsFeedFilter(account), scope, LocalCache)
|
val shortsFeed = FeedContentState(ShortsFeedFilter(account), scope, LocalCache)
|
||||||
@@ -114,6 +118,8 @@ class AccountFeedContentStates(
|
|||||||
discoverPublicChats.updateFeedWith(newNotes)
|
discoverPublicChats.updateFeedWith(newNotes)
|
||||||
|
|
||||||
pollsFeed.updateFeedWith(newNotes)
|
pollsFeed.updateFeedWith(newNotes)
|
||||||
|
openPollsFeed.updateFeedWith(newNotes)
|
||||||
|
closedPollsFeed.updateFeedWith(newNotes)
|
||||||
|
|
||||||
picturesFeed.updateFeedWith(newNotes)
|
picturesFeed.updateFeedWith(newNotes)
|
||||||
shortsFeed.updateFeedWith(newNotes)
|
shortsFeed.updateFeedWith(newNotes)
|
||||||
@@ -148,6 +154,8 @@ class AccountFeedContentStates(
|
|||||||
discoverPublicChats.deleteFromFeed(newNotes)
|
discoverPublicChats.deleteFromFeed(newNotes)
|
||||||
|
|
||||||
pollsFeed.deleteFromFeed(newNotes)
|
pollsFeed.deleteFromFeed(newNotes)
|
||||||
|
openPollsFeed.deleteFromFeed(newNotes)
|
||||||
|
closedPollsFeed.deleteFromFeed(newNotes)
|
||||||
|
|
||||||
picturesFeed.deleteFromFeed(newNotes)
|
picturesFeed.deleteFromFeed(newNotes)
|
||||||
shortsFeed.deleteFromFeed(newNotes)
|
shortsFeed.deleteFromFeed(newNotes)
|
||||||
|
|||||||
+110
-16
@@ -21,24 +21,41 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.polls
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.polls
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
|
import androidx.compose.foundation.pager.PagerState
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.SecondaryTabRow
|
||||||
|
import androidx.compose.material3.Tab
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
|
||||||
|
import com.vitorpamplona.amethyst.ui.feeds.PagerStateKeys
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.RenderFeedContentState
|
import com.vitorpamplona.amethyst.ui.feeds.RenderFeedContentState
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.SaveableFeedContentState
|
import com.vitorpamplona.amethyst.ui.feeds.SaveableFeedContentState
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys
|
import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.feeds.rememberForeverPagerState
|
||||||
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
|
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
|
||||||
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
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.datasource.PollsFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.datasource.PollsFilterAssemblerSubscription
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PollsScreen(
|
fun PollsScreen(
|
||||||
@@ -46,7 +63,8 @@ fun PollsScreen(
|
|||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
PollsScreen(
|
PollsScreen(
|
||||||
pollsFeedContentState = accountViewModel.feedStates.pollsFeed,
|
openPollsFeedContentState = accountViewModel.feedStates.openPollsFeed,
|
||||||
|
closedPollsFeedContentState = accountViewModel.feedStates.closedPollsFeed,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
)
|
)
|
||||||
@@ -54,23 +72,85 @@ fun PollsScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PollsScreen(
|
fun PollsScreen(
|
||||||
pollsFeedContentState: FeedContentState,
|
openPollsFeedContentState: FeedContentState,
|
||||||
|
closedPollsFeedContentState: FeedContentState,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
WatchLifecycleAndUpdateModel(pollsFeedContentState)
|
WatchLifecycleAndUpdateModel(openPollsFeedContentState)
|
||||||
WatchAccountForPollsScreen(pollsFeedContentState = pollsFeedContentState, accountViewModel = accountViewModel)
|
WatchLifecycleAndUpdateModel(closedPollsFeedContentState)
|
||||||
|
WatchAccountForPollsScreen(openPollsFeedContentState, closedPollsFeedContentState, accountViewModel)
|
||||||
PollsFilterAssemblerSubscription(accountViewModel)
|
PollsFilterAssemblerSubscription(accountViewModel)
|
||||||
|
|
||||||
|
AssemblePollsTabs(openPollsFeedContentState, closedPollsFeedContentState) { pagerState, tabItems ->
|
||||||
|
PollsPages(pagerState, tabItems, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AssemblePollsTabs(
|
||||||
|
openPollsFeedContentState: FeedContentState,
|
||||||
|
closedPollsFeedContentState: FeedContentState,
|
||||||
|
inner: @Composable (PagerState, ImmutableList<PollsTabItem>) -> Unit,
|
||||||
|
) {
|
||||||
|
val pagerState = rememberForeverPagerState(key = PagerStateKeys.POLLS_SCREEN) { 2 }
|
||||||
|
|
||||||
|
val tabs by
|
||||||
|
remember(openPollsFeedContentState, closedPollsFeedContentState) {
|
||||||
|
mutableStateOf(
|
||||||
|
listOf(
|
||||||
|
PollsTabItem(
|
||||||
|
resource = R.string.open_polls,
|
||||||
|
feedState = openPollsFeedContentState,
|
||||||
|
routeForLastRead = "PollsOpenFeed",
|
||||||
|
scrollStateKey = ScrollStateKeys.POLLS_OPEN,
|
||||||
|
),
|
||||||
|
PollsTabItem(
|
||||||
|
resource = R.string.closed_polls,
|
||||||
|
feedState = closedPollsFeedContentState,
|
||||||
|
routeForLastRead = "PollsClosedFeed",
|
||||||
|
scrollStateKey = ScrollStateKeys.POLLS_CLOSED,
|
||||||
|
),
|
||||||
|
).toImmutableList(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
inner(pagerState, tabs)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PollsPages(
|
||||||
|
pagerState: PagerState,
|
||||||
|
tabs: ImmutableList<PollsTabItem>,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
DisappearingScaffold(
|
DisappearingScaffold(
|
||||||
isInvertedLayout = false,
|
isInvertedLayout = false,
|
||||||
topBar = {
|
topBar = {
|
||||||
PollsTopBar(accountViewModel, nav)
|
Column {
|
||||||
|
PollsTopBar(accountViewModel, nav)
|
||||||
|
SecondaryTabRow(
|
||||||
|
containerColor = Color.Transparent,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||||
|
modifier = TabRowHeight,
|
||||||
|
selectedTabIndex = pagerState.currentPage,
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
tabs.forEachIndexed { index, tab ->
|
||||||
|
Tab(
|
||||||
|
selected = pagerState.currentPage == index,
|
||||||
|
text = { Text(text = stringRes(tab.resource)) },
|
||||||
|
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(index) } },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
AppBottomBar(Route.Polls, accountViewModel) { route ->
|
AppBottomBar(Route.Polls, accountViewModel) { route ->
|
||||||
if (route == Route.Polls) {
|
if (route == Route.Polls) {
|
||||||
pollsFeedContentState.sendToTop()
|
tabs[pagerState.currentPage].feedState.sendToTop()
|
||||||
} else {
|
} else {
|
||||||
nav.newStack(route)
|
nav.newStack(route)
|
||||||
}
|
}
|
||||||
@@ -80,16 +160,20 @@ fun PollsScreen(
|
|||||||
NewPollButton(nav)
|
NewPollButton(nav)
|
||||||
},
|
},
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
) { paddingValues ->
|
) {
|
||||||
Column(Modifier.padding(paddingValues)) {
|
HorizontalPager(
|
||||||
RefresheableBox(pollsFeedContentState, true) {
|
contentPadding = it,
|
||||||
SaveableFeedContentState(pollsFeedContentState, scrollStateKey = ScrollStateKeys.POLLS_SCREEN) { listState ->
|
state = pagerState,
|
||||||
|
userScrollEnabled = true,
|
||||||
|
) { page ->
|
||||||
|
RefresheableBox(tabs[page].feedState, true) {
|
||||||
|
SaveableFeedContentState(tabs[page].feedState, scrollStateKey = tabs[page].scrollStateKey) { listState ->
|
||||||
RenderFeedContentState(
|
RenderFeedContentState(
|
||||||
feedContentState = pollsFeedContentState,
|
feedContentState = tabs[page].feedState,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
listState = listState,
|
listState = listState,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
routeForLastRead = "PollsFeed",
|
routeForLastRead = tabs[page].routeForLastRead,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,7 +183,8 @@ fun PollsScreen(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WatchAccountForPollsScreen(
|
fun WatchAccountForPollsScreen(
|
||||||
pollsFeedContentState: FeedContentState,
|
openPollsFeedContentState: FeedContentState,
|
||||||
|
closedPollsFeedContentState: FeedContentState,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val listState by accountViewModel.account.livePollsFollowLists.collectAsStateWithLifecycle()
|
val listState by accountViewModel.account.livePollsFollowLists.collectAsStateWithLifecycle()
|
||||||
@@ -108,6 +193,15 @@ fun WatchAccountForPollsScreen(
|
|||||||
.collectAsStateWithLifecycle()
|
.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
LaunchedEffect(accountViewModel, listState, hiddenUsers) {
|
LaunchedEffect(accountViewModel, listState, hiddenUsers) {
|
||||||
pollsFeedContentState.checkKeysInvalidateDataAndSendToTop()
|
openPollsFeedContentState.checkKeysInvalidateDataAndSendToTop()
|
||||||
|
closedPollsFeedContentState.checkKeysInvalidateDataAndSendToTop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
class PollsTabItem(
|
||||||
|
val resource: Int,
|
||||||
|
val feedState: FeedContentState,
|
||||||
|
val routeForLastRead: String,
|
||||||
|
val scrollStateKey: String,
|
||||||
|
)
|
||||||
|
|||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* 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.polls.dal
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.TopFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
|
||||||
|
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||||
|
import com.vitorpamplona.quartz.nip88Polls.poll.PollEvent
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
class ClosedPollsFeedFilter(
|
||||||
|
val account: Account,
|
||||||
|
) : AdditiveFeedFilter<Note>() {
|
||||||
|
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList().code + "-closed"
|
||||||
|
|
||||||
|
override fun limit() = 200
|
||||||
|
|
||||||
|
fun followList(): TopFilter = account.settings.defaultPollsFollowList.value
|
||||||
|
|
||||||
|
fun TopFilter.isMuteList() = this is TopFilter.MuteList
|
||||||
|
|
||||||
|
fun TopFilter.isBlockList() = this is TopFilter.PeopleList && this.address == account.blockPeopleList.getBlockListAddress()
|
||||||
|
|
||||||
|
fun TopFilter.wantsToSeeNegativeStuff() = isMuteList() || isBlockList()
|
||||||
|
|
||||||
|
override fun showHiddenKey(): Boolean = followList().wantsToSeeNegativeStuff()
|
||||||
|
|
||||||
|
private fun isClosed(note: Note): Boolean {
|
||||||
|
val noteEvent = note.event
|
||||||
|
return when (noteEvent) {
|
||||||
|
is PollEvent -> {
|
||||||
|
noteEvent.hasEnded()
|
||||||
|
}
|
||||||
|
|
||||||
|
is ZapPollEvent -> {
|
||||||
|
val closedAt = noteEvent.closedAt()
|
||||||
|
closedAt != null && closedAt < TimeUtils.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun feed(): List<Note> {
|
||||||
|
val params = buildFilterParams(account)
|
||||||
|
val notes =
|
||||||
|
LocalCache.notes.filterIntoSet { _, it ->
|
||||||
|
val noteEvent = it.event
|
||||||
|
(noteEvent is PollEvent || noteEvent is ZapPollEvent) && params.match(noteEvent, it.relays) && isClosed(it)
|
||||||
|
}
|
||||||
|
return sort(notes)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun applyFilter(newItems: Set<Note>): Set<Note> = innerApplyFilter(newItems)
|
||||||
|
|
||||||
|
fun buildFilterParams(account: Account): FilterByListParams =
|
||||||
|
FilterByListParams.create(
|
||||||
|
account.livePollsFollowLists.value,
|
||||||
|
account.hiddenUsers.flow.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
||||||
|
val params = buildFilterParams(account)
|
||||||
|
|
||||||
|
return collection.filterTo(HashSet()) {
|
||||||
|
val noteEvent = it.event
|
||||||
|
(noteEvent is PollEvent || noteEvent is ZapPollEvent) && params.match(noteEvent, it.relays) && isClosed(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sort(items: Set<Note>): List<Note> = items.sortedWith(DefaultFeedOrder)
|
||||||
|
}
|
||||||
+97
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* 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.polls.dal
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.TopFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
|
||||||
|
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||||
|
import com.vitorpamplona.quartz.nip88Polls.poll.PollEvent
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
class OpenPollsFeedFilter(
|
||||||
|
val account: Account,
|
||||||
|
) : AdditiveFeedFilter<Note>() {
|
||||||
|
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList().code + "-open"
|
||||||
|
|
||||||
|
override fun limit() = 200
|
||||||
|
|
||||||
|
fun followList(): TopFilter = account.settings.defaultPollsFollowList.value
|
||||||
|
|
||||||
|
fun TopFilter.isMuteList() = this is TopFilter.MuteList
|
||||||
|
|
||||||
|
fun TopFilter.isBlockList() = this is TopFilter.PeopleList && this.address == account.blockPeopleList.getBlockListAddress()
|
||||||
|
|
||||||
|
fun TopFilter.wantsToSeeNegativeStuff() = isMuteList() || isBlockList()
|
||||||
|
|
||||||
|
override fun showHiddenKey(): Boolean = followList().wantsToSeeNegativeStuff()
|
||||||
|
|
||||||
|
private fun isOpen(note: Note): Boolean {
|
||||||
|
val noteEvent = note.event
|
||||||
|
return when (noteEvent) {
|
||||||
|
is PollEvent -> {
|
||||||
|
!noteEvent.hasEnded()
|
||||||
|
}
|
||||||
|
|
||||||
|
is ZapPollEvent -> {
|
||||||
|
val closedAt = noteEvent.closedAt()
|
||||||
|
closedAt == null || closedAt >= TimeUtils.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun feed(): List<Note> {
|
||||||
|
val params = buildFilterParams(account)
|
||||||
|
val notes =
|
||||||
|
LocalCache.notes.filterIntoSet { _, it ->
|
||||||
|
val noteEvent = it.event
|
||||||
|
(noteEvent is PollEvent || noteEvent is ZapPollEvent) && params.match(noteEvent, it.relays) && isOpen(it)
|
||||||
|
}
|
||||||
|
return sort(notes)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun applyFilter(newItems: Set<Note>): Set<Note> = innerApplyFilter(newItems)
|
||||||
|
|
||||||
|
fun buildFilterParams(account: Account): FilterByListParams =
|
||||||
|
FilterByListParams.create(
|
||||||
|
account.livePollsFollowLists.value,
|
||||||
|
account.hiddenUsers.flow.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
||||||
|
val params = buildFilterParams(account)
|
||||||
|
|
||||||
|
return collection.filterTo(HashSet()) {
|
||||||
|
val noteEvent = it.event
|
||||||
|
(noteEvent is PollEvent || noteEvent is ZapPollEvent) && params.match(noteEvent, it.relays) && isOpen(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sort(items: Set<Note>): List<Note> = items.sortedWith(DefaultFeedOrder)
|
||||||
|
}
|
||||||
@@ -416,6 +416,8 @@
|
|||||||
<string name="migrate_bookmarks_success">Bookmarks migrated successfully</string>
|
<string name="migrate_bookmarks_success">Bookmarks migrated successfully</string>
|
||||||
<string name="drafts">Drafts</string>
|
<string name="drafts">Drafts</string>
|
||||||
<string name="polls">Polls</string>
|
<string name="polls">Polls</string>
|
||||||
|
<string name="open_polls">Open</string>
|
||||||
|
<string name="closed_polls">Closed</string>
|
||||||
<string name="pictures">Pictures</string>
|
<string name="pictures">Pictures</string>
|
||||||
<string name="shorts">Shorts</string>
|
<string name="shorts">Shorts</string>
|
||||||
<string name="longs">Videos</string>
|
<string name="longs">Videos</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user