Adds Long Video Menu item
This commit is contained in:
+3
@@ -37,6 +37,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.feed.datasource
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.datasource.GeoHashFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.datasource.GeoHashFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.datasource.HashtagFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.datasource.HashtagFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeFilterAssembler
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.datasource.LongsFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.pictures.datasource.PicturesFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.pictures.datasource.PicturesFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.datasource.PollsFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.datasource.PollsFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.datasource.UserProfileFilterAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.datasource.UserProfileFilterAssembler
|
||||||
@@ -91,6 +92,7 @@ class RelaySubscriptionsCoordinator(
|
|||||||
val polls = PollsFilterAssembler(client)
|
val polls = PollsFilterAssembler(client)
|
||||||
val pictures = PicturesFilterAssembler(client)
|
val pictures = PicturesFilterAssembler(client)
|
||||||
val shorts = ShortsFilterAssembler(client)
|
val shorts = ShortsFilterAssembler(client)
|
||||||
|
val longs = LongsFilterAssembler(client)
|
||||||
|
|
||||||
// active when sending zaps via NWC
|
// active when sending zaps via NWC
|
||||||
val nwc = NWCPaymentFilterAssembler(client)
|
val nwc = NWCPaymentFilterAssembler(client)
|
||||||
@@ -105,6 +107,7 @@ class RelaySubscriptionsCoordinator(
|
|||||||
polls,
|
polls,
|
||||||
pictures,
|
pictures,
|
||||||
shorts,
|
shorts,
|
||||||
|
longs,
|
||||||
channelFinder,
|
channelFinder,
|
||||||
eventFinder,
|
eventFinder,
|
||||||
userFinder,
|
userFinder,
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ object ScrollStateKeys {
|
|||||||
const val POLLS_SCREEN = "PollsFeed"
|
const val POLLS_SCREEN = "PollsFeed"
|
||||||
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 SEARCH_SCREEN = "SearchFeed"
|
const val SEARCH_SCREEN = "SearchFeed"
|
||||||
|
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.ListOfPeopleList
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.metadata.FollowPackMetadataScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.metadata.FollowPackMetadataScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.metadata.PeopleListMetadataScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.metadata.PeopleListMetadataScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.memberEdit.FollowListAndPackAndUserScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.memberEdit.FollowListAndPackAndUserScreen
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.LongsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.newUser.ImportFollowListPickFollowsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.newUser.ImportFollowListPickFollowsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.newUser.ImportFollowListSelectUserScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.newUser.ImportFollowListSelectUserScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationScreen
|
||||||
@@ -181,6 +182,7 @@ fun BuildNavigation(
|
|||||||
composableFromEnd<Route.Polls> { PollsScreen(accountViewModel, nav) }
|
composableFromEnd<Route.Polls> { PollsScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.Pictures> { PicturesScreen(accountViewModel, nav) }
|
composableFromEnd<Route.Pictures> { PicturesScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.Shorts> { ShortsScreen(accountViewModel, nav) }
|
composableFromEnd<Route.Shorts> { ShortsScreen(accountViewModel, nav) }
|
||||||
|
composableFromEnd<Route.Longs> { LongsScreen(accountViewModel, nav) }
|
||||||
composable<Route.Chess> { ChessLobbyScreen(accountViewModel, nav) }
|
composable<Route.Chess> { ChessLobbyScreen(accountViewModel, nav) }
|
||||||
|
|
||||||
composableFromEnd<Route.Wallet> { WalletScreen(accountViewModel, nav) }
|
composableFromEnd<Route.Wallet> { WalletScreen(accountViewModel, nav) }
|
||||||
|
|||||||
+9
@@ -61,6 +61,7 @@ import androidx.compose.material.icons.outlined.Language
|
|||||||
import androidx.compose.material.icons.outlined.Photo
|
import androidx.compose.material.icons.outlined.Photo
|
||||||
import androidx.compose.material.icons.outlined.PlayCircle
|
import androidx.compose.material.icons.outlined.PlayCircle
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
|
import androidx.compose.material.icons.outlined.SmartDisplay
|
||||||
import androidx.compose.material.icons.outlined.Sync
|
import androidx.compose.material.icons.outlined.Sync
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -599,6 +600,14 @@ fun ListContent(
|
|||||||
route = Route.Shorts,
|
route = Route.Shorts,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
NavigationRow(
|
||||||
|
title = R.string.longs,
|
||||||
|
icon = Icons.Outlined.SmartDisplay,
|
||||||
|
tint = MaterialTheme.colorScheme.onBackground,
|
||||||
|
nav = nav,
|
||||||
|
route = Route.Longs,
|
||||||
|
)
|
||||||
|
|
||||||
NavigationRow(
|
NavigationRow(
|
||||||
title = R.string.wallet,
|
title = R.string.wallet,
|
||||||
icon = Icons.Outlined.AccountBalanceWallet,
|
icon = Icons.Outlined.AccountBalanceWallet,
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ sealed class Route {
|
|||||||
|
|
||||||
@Serializable object Shorts : Route()
|
@Serializable object Shorts : Route()
|
||||||
|
|
||||||
|
@Serializable object Longs : Route()
|
||||||
|
|
||||||
@Serializable object Chess : Route()
|
@Serializable object Chess : Route()
|
||||||
|
|
||||||
@Serializable object Wallet : Route()
|
@Serializable object Wallet : Route()
|
||||||
|
|||||||
+4
@@ -40,6 +40,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.drafts.dal.DraftEventsFeedF
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.dal.HomeConversationsFeedFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.dal.HomeConversationsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.dal.HomeLiveFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.dal.HomeLiveFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.dal.HomeNewThreadFeedFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.dal.HomeNewThreadFeedFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.dal.LongsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.CardFeedContentState
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.CardFeedContentState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationSummaryState
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationSummaryState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.OpenPollsState
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.OpenPollsState
|
||||||
@@ -76,6 +77,7 @@ class AccountFeedContentStates(
|
|||||||
|
|
||||||
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)
|
||||||
|
val longsFeed = FeedContentState(LongsFeedFilter(account), scope, LocalCache)
|
||||||
|
|
||||||
val notifications = CardFeedContentState(NotificationFeedFilter(account), scope)
|
val notifications = CardFeedContentState(NotificationFeedFilter(account), scope)
|
||||||
val notificationsOpenPolls = OpenPollsState(account, scope)
|
val notificationsOpenPolls = OpenPollsState(account, scope)
|
||||||
@@ -115,6 +117,7 @@ class AccountFeedContentStates(
|
|||||||
|
|
||||||
picturesFeed.updateFeedWith(newNotes)
|
picturesFeed.updateFeedWith(newNotes)
|
||||||
shortsFeed.updateFeedWith(newNotes)
|
shortsFeed.updateFeedWith(newNotes)
|
||||||
|
longsFeed.updateFeedWith(newNotes)
|
||||||
|
|
||||||
notifications.updateFeedWith(newNotes)
|
notifications.updateFeedWith(newNotes)
|
||||||
notificationSummary.invalidateInsertData(newNotes)
|
notificationSummary.invalidateInsertData(newNotes)
|
||||||
@@ -148,6 +151,7 @@ class AccountFeedContentStates(
|
|||||||
|
|
||||||
picturesFeed.deleteFromFeed(newNotes)
|
picturesFeed.deleteFromFeed(newNotes)
|
||||||
shortsFeed.deleteFromFeed(newNotes)
|
shortsFeed.deleteFromFeed(newNotes)
|
||||||
|
longsFeed.deleteFromFeed(newNotes)
|
||||||
|
|
||||||
notifications.deleteFromFeed(newNotes)
|
notifications.deleteFromFeed(newNotes)
|
||||||
notificationSummary.invalidateInsertData(newNotes)
|
notificationSummary.invalidateInsertData(newNotes)
|
||||||
|
|||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* 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.longs
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.shorts.VideoCardCompose
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||||
|
import com.vitorpamplona.quartz.nip71Video.VideoEvent
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LongsFeedLoaded(
|
||||||
|
loaded: FeedState.Loaded,
|
||||||
|
listState: LazyListState,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
val items by loaded.feed.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = FeedPadding,
|
||||||
|
state = listState,
|
||||||
|
) {
|
||||||
|
itemsIndexed(
|
||||||
|
items.list,
|
||||||
|
key = { _, item -> item.idHex },
|
||||||
|
contentType = { _, item -> item.event?.kind ?: -1 },
|
||||||
|
) { _, item ->
|
||||||
|
if (item.event is VideoEvent) {
|
||||||
|
VideoCardCompose(
|
||||||
|
baseNote = item,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(
|
||||||
|
thickness = DividerThickness,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+118
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* 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.longs
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
|
||||||
|
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
||||||
|
import com.vitorpamplona.amethyst.ui.feeds.RenderFeedContentState
|
||||||
|
import com.vitorpamplona.amethyst.ui.feeds.SaveableFeedContentState
|
||||||
|
import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys
|
||||||
|
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.datasource.LongsFilterAssemblerSubscription
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LongsScreen(
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
LongsScreen(
|
||||||
|
longsFeedContentState = accountViewModel.feedStates.longsFeed,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LongsScreen(
|
||||||
|
longsFeedContentState: FeedContentState,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
WatchLifecycleAndUpdateModel(longsFeedContentState)
|
||||||
|
WatchAccountForLongsScreen(videoFeedState = longsFeedContentState, accountViewModel = accountViewModel)
|
||||||
|
LongsFilterAssemblerSubscription(accountViewModel)
|
||||||
|
|
||||||
|
DisappearingScaffold(
|
||||||
|
isInvertedLayout = false,
|
||||||
|
topBar = {
|
||||||
|
LongsTopBar(accountViewModel, nav)
|
||||||
|
},
|
||||||
|
bottomBar = {
|
||||||
|
AppBottomBar(Route.Longs, accountViewModel) { route ->
|
||||||
|
if (route == Route.Longs) {
|
||||||
|
longsFeedContentState.sendToTop()
|
||||||
|
} else {
|
||||||
|
nav.newStack(route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(Modifier.padding(paddingValues)) {
|
||||||
|
RefresheableBox(longsFeedContentState, true) {
|
||||||
|
SaveableFeedContentState(longsFeedContentState, scrollStateKey = ScrollStateKeys.LONGS_SCREEN) { listState ->
|
||||||
|
RenderFeedContentState(
|
||||||
|
feedContentState = longsFeedContentState,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
listState = listState,
|
||||||
|
nav = nav,
|
||||||
|
routeForLastRead = "LongsFeed",
|
||||||
|
onLoaded = { loaded ->
|
||||||
|
LongsFeedLoaded(
|
||||||
|
loaded = loaded,
|
||||||
|
listState = listState,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WatchAccountForLongsScreen(
|
||||||
|
videoFeedState: FeedContentState,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
) {
|
||||||
|
val listState by accountViewModel.account.liveDiscoveryFollowLists.collectAsStateWithLifecycle()
|
||||||
|
val hiddenUsers =
|
||||||
|
accountViewModel.account.hiddenUsers.flow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
LaunchedEffect(accountViewModel, listState, hiddenUsers) {
|
||||||
|
videoFeedState.checkKeysInvalidateDataAndSendToTop()
|
||||||
|
}
|
||||||
|
}
|
||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* 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.longs
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.TopFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.topbars.FeedFilterSpinner
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.topbars.UserDrawerSearchTopBar
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.FeedDefinition
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.TopNavFilterState
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LongsTopBar(
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
UserDrawerSearchTopBar(accountViewModel, nav) {
|
||||||
|
val list by accountViewModel.account.settings.defaultLongsFollowList
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
LongsTopNavFilterBar(
|
||||||
|
followListsModel = accountViewModel.feedStates.feedListOptions,
|
||||||
|
listName = list,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
onChange = accountViewModel.account.settings::changeDefaultDiscoveryFollowList,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LongsTopNavFilterBar(
|
||||||
|
followListsModel: TopNavFilterState,
|
||||||
|
listName: TopFilter,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
onChange: (FeedDefinition) -> Unit,
|
||||||
|
) {
|
||||||
|
val allLists by followListsModel.kind3GlobalPeople.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
FeedFilterSpinner(
|
||||||
|
placeholderCode = listName,
|
||||||
|
explainer = stringRes(R.string.select_list_to_filter),
|
||||||
|
options = allLists,
|
||||||
|
onSelect = { onChange(allLists.getOrNull(it) ?: followListsModel.allFollows) },
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
+118
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* 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.longs.dal
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.TopFilter
|
||||||
|
import com.vitorpamplona.amethyst.model.filterIntoSet
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.dal.SupportedContent
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.SUPPORTED_VIDEO_FEED_MIME_TYPES_SET
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||||
|
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
||||||
|
import com.vitorpamplona.quartz.nip71Video.VideoMeta
|
||||||
|
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
|
||||||
|
import kotlin.collections.plus
|
||||||
|
|
||||||
|
class LongsFeedFilter(
|
||||||
|
val account: Account,
|
||||||
|
) : AdditiveFeedFilter<Note>() {
|
||||||
|
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList().code
|
||||||
|
|
||||||
|
override fun limit() = 200
|
||||||
|
|
||||||
|
fun followList(): TopFilter = account.settings.defaultLongsFollowList.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()
|
||||||
|
|
||||||
|
override fun feed(): List<Note> {
|
||||||
|
val params = buildFilterParams(account)
|
||||||
|
val notes =
|
||||||
|
LocalCache.notes.filterIntoSet { _, it ->
|
||||||
|
acceptableEvent(it, params)
|
||||||
|
} +
|
||||||
|
LocalCache.addressables.filterIntoSet(VideoHorizontalEvent.KIND) { _, it ->
|
||||||
|
acceptableEvent(it, params)
|
||||||
|
}
|
||||||
|
return sort(notes)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun applyFilter(newItems: Set<Note>): Set<Note> = innerApplyFilter(newItems)
|
||||||
|
|
||||||
|
fun buildFilterParams(account: Account): FilterByListParams =
|
||||||
|
FilterByListParams.create(
|
||||||
|
account.liveDiscoveryFollowLists.value,
|
||||||
|
account.hiddenUsers.flow.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
||||||
|
val params = buildFilterParams(account)
|
||||||
|
|
||||||
|
return collection.filterTo(HashSet()) {
|
||||||
|
acceptableEvent(it, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val videoFeedSupport =
|
||||||
|
SupportedContent(
|
||||||
|
blockedUrls = listOf("youtu.be", "youtube.com"),
|
||||||
|
mimeTypes = SUPPORTED_VIDEO_FEED_MIME_TYPES_SET,
|
||||||
|
supportedFileExtensions = (RichTextParser.videoExtensions + RichTextParser.imageExtensions).toSet(),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun acceptableVideoiMetas(iMetas: List<VideoMeta>): Boolean = iMetas.any { videoFeedSupport.acceptableUrl(it.url, it.mimeType) }
|
||||||
|
|
||||||
|
fun acceptanceEvent(noteEvent: VideoHorizontalEvent) = acceptableVideoiMetas(noteEvent.imetaTags())
|
||||||
|
|
||||||
|
fun acceptanceEvent(noteEvent: VideoNormalEvent) = acceptableVideoiMetas(noteEvent.imetaTags())
|
||||||
|
|
||||||
|
fun acceptableEvent(
|
||||||
|
note: Note,
|
||||||
|
params: FilterByListParams,
|
||||||
|
): Boolean {
|
||||||
|
val noteEvent = note.event
|
||||||
|
|
||||||
|
if (noteEvent is AddressableEvent && note !is AddressableNote) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
(noteEvent is VideoHorizontalEvent && acceptanceEvent(noteEvent)) ||
|
||||||
|
(noteEvent is VideoNormalEvent && acceptanceEvent(noteEvent))
|
||||||
|
) &&
|
||||||
|
params.match(noteEvent, note.relays) &&
|
||||||
|
(params.isHiddenList || account.isAcceptable(note))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sort(items: Set<Note>): List<Note> = items.sortedWith(DefaultFeedOrder)
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* 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.longs.datasource
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountFeedContentStates
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
|
class LongsQueryState(
|
||||||
|
val account: Account,
|
||||||
|
val feedStates: AccountFeedContentStates,
|
||||||
|
val scope: CoroutineScope,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class LongsFilterAssembler(
|
||||||
|
client: INostrClient,
|
||||||
|
) : ComposeSubscriptionManager<LongsQueryState>() {
|
||||||
|
val group =
|
||||||
|
listOf(
|
||||||
|
LongsSubAssembler(client, ::allKeys),
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun invalidateKeys() = invalidateFilters()
|
||||||
|
|
||||||
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|
||||||
|
override fun destroy() = group.forEach { it.destroy() }
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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.longs.datasource
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.KeyDataSourceSubscription
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LongsFilterAssemblerSubscription(accountViewModel: AccountViewModel) {
|
||||||
|
LongsFilterAssemblerSubscription(
|
||||||
|
accountViewModel.dataSources().longs,
|
||||||
|
accountViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LongsFilterAssemblerSubscription(
|
||||||
|
dataSource: LongsFilterAssembler,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
) {
|
||||||
|
val state =
|
||||||
|
remember(accountViewModel.account) {
|
||||||
|
LongsQueryState(accountViewModel.account, accountViewModel.feedStates, accountViewModel.viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyDataSourceSubscription(state, dataSource)
|
||||||
|
}
|
||||||
+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.longs.datasource
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.TopFilter
|
||||||
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserAndFollowListEoseManager
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.subscriptions.Subscription
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.sample
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class LongsSubAssembler(
|
||||||
|
client: INostrClient,
|
||||||
|
allKeys: () -> Set<LongsQueryState>,
|
||||||
|
) : PerUserAndFollowListEoseManager<LongsQueryState, TopFilter>(client, allKeys) {
|
||||||
|
override fun updateFilter(
|
||||||
|
key: LongsQueryState,
|
||||||
|
since: SincePerRelayMap?,
|
||||||
|
): List<RelayBasedFilter> {
|
||||||
|
val feedSettings = key.followsPerRelay()
|
||||||
|
|
||||||
|
return makeLongsFilter(feedSettings, since, key.feedStates.longsFeed.lastNoteCreatedAtIfFilled())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun user(key: LongsQueryState) = key.account.userProfile()
|
||||||
|
|
||||||
|
override fun list(key: LongsQueryState) = key.listName()
|
||||||
|
|
||||||
|
fun LongsQueryState.listNameFlow() = account.settings.defaultLongsFollowList
|
||||||
|
|
||||||
|
fun LongsQueryState.listName() = listNameFlow().value
|
||||||
|
|
||||||
|
fun LongsQueryState.followsPerRelayFlow() = account.liveDiscoveryFollowListsPerRelay
|
||||||
|
|
||||||
|
fun LongsQueryState.followsPerRelay() = followsPerRelayFlow().value
|
||||||
|
|
||||||
|
val userJobMap = mutableMapOf<User, List<Job>>()
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
|
override fun newSub(key: LongsQueryState): Subscription {
|
||||||
|
val user = user(key)
|
||||||
|
userJobMap[user]?.forEach { it.cancel() }
|
||||||
|
userJobMap[user] =
|
||||||
|
listOf(
|
||||||
|
key.scope.launch(Dispatchers.IO) {
|
||||||
|
key.listNameFlow().collectLatest {
|
||||||
|
invalidateFilters()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key.scope.launch(Dispatchers.IO) {
|
||||||
|
key.followsPerRelayFlow().sample(500).collectLatest {
|
||||||
|
invalidateFilters()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key.account.scope.launch(Dispatchers.IO) {
|
||||||
|
key.feedStates.longsFeed.lastNoteCreatedAtWhenFullyLoaded.sample(5000).collectLatest {
|
||||||
|
invalidateFilters()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return super.newSub(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun endSub(
|
||||||
|
key: User,
|
||||||
|
subId: String,
|
||||||
|
) {
|
||||||
|
super.endSub(key, subId)
|
||||||
|
userJobMap[key]?.forEach { it.cancel() }
|
||||||
|
}
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.longs.datasource
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavPerRelayFilterSet
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavPerRelayFilterSet
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagTopNavPerRelayFilterSet
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.datasource.subassemblies.filterLongsByAuthors
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.datasource.subassemblies.filterLongsByFollows
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.datasource.subassemblies.filterLongsByHashtag
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.datasource.subassemblies.filterLongsByMutedAuthors
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.datasource.subassemblies.filterLongsGlobal
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
|
||||||
|
fun makeLongsFilter(
|
||||||
|
feedSettings: IFeedTopNavPerRelayFilterSet,
|
||||||
|
since: SincePerRelayMap?,
|
||||||
|
defaultSince: Long? = null,
|
||||||
|
): List<RelayBasedFilter> =
|
||||||
|
when (feedSettings) {
|
||||||
|
is AllFollowsTopNavPerRelayFilterSet -> filterLongsByFollows(feedSettings, since, defaultSince)
|
||||||
|
is AuthorsTopNavPerRelayFilterSet -> filterLongsByAuthors(feedSettings, since, defaultSince)
|
||||||
|
is GlobalTopNavPerRelayFilterSet -> filterLongsGlobal(feedSettings, since, defaultSince)
|
||||||
|
is HashtagTopNavPerRelayFilterSet -> filterLongsByHashtag(feedSettings, since, defaultSince)
|
||||||
|
is MutedAuthorsTopNavPerRelayFilterSet -> filterLongsByMutedAuthors(feedSettings, since, defaultSince)
|
||||||
|
else -> emptyList()
|
||||||
|
}
|
||||||
+93
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* 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.longs.datasource.subassemblies
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.author.AuthorsTopNavPerRelayFilterSet
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.noteBased.muted.MutedAuthorsTopNavPerRelayFilterSet
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
||||||
|
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
|
||||||
|
|
||||||
|
fun filterLongsByAuthors(
|
||||||
|
relay: NormalizedRelayUrl,
|
||||||
|
authors: Set<HexKey>,
|
||||||
|
since: Long? = null,
|
||||||
|
): List<RelayBasedFilter> {
|
||||||
|
val authorList = authors.sorted()
|
||||||
|
return listOf(
|
||||||
|
RelayBasedFilter(
|
||||||
|
relay = relay,
|
||||||
|
filter =
|
||||||
|
Filter(
|
||||||
|
authors = authorList,
|
||||||
|
kinds = listOf(VideoNormalEvent.KIND, VideoHorizontalEvent.KIND),
|
||||||
|
limit = 200,
|
||||||
|
since = since,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun filterLongsByAuthors(
|
||||||
|
authorSet: AuthorsTopNavPerRelayFilterSet,
|
||||||
|
since: SincePerRelayMap?,
|
||||||
|
defaultSince: Long? = null,
|
||||||
|
): List<RelayBasedFilter> {
|
||||||
|
if (authorSet.set.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
return authorSet.set
|
||||||
|
.mapNotNull {
|
||||||
|
if (it.value.authors.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
filterLongsByAuthors(
|
||||||
|
relay = it.key,
|
||||||
|
authors = it.value.authors,
|
||||||
|
since = since?.get(it.key)?.time ?: defaultSince,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun filterLongsByMutedAuthors(
|
||||||
|
authorSet: MutedAuthorsTopNavPerRelayFilterSet,
|
||||||
|
since: SincePerRelayMap?,
|
||||||
|
defaultSince: Long? = null,
|
||||||
|
): List<RelayBasedFilter> {
|
||||||
|
if (authorSet.set.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
return authorSet.set
|
||||||
|
.mapNotNull {
|
||||||
|
if (it.value.authors.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
filterLongsByAuthors(
|
||||||
|
relay = it.key,
|
||||||
|
authors = it.value.authors,
|
||||||
|
since = since?.get(it.key)?.time ?: defaultSince,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.flatten()
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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.longs.datasource.subassemblies
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.allFollows.AllFollowsTopNavPerRelayFilterSet
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
|
||||||
|
fun filterLongsByFollows(
|
||||||
|
followsSet: AllFollowsTopNavPerRelayFilterSet,
|
||||||
|
since: SincePerRelayMap?,
|
||||||
|
defaultSince: Long? = null,
|
||||||
|
): List<RelayBasedFilter> {
|
||||||
|
if (followsSet.set.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
return followsSet.set.flatMap {
|
||||||
|
val since = since?.get(it.key)?.time ?: defaultSince
|
||||||
|
val relay = it.key
|
||||||
|
|
||||||
|
listOfNotNull(
|
||||||
|
it.value.authors?.let {
|
||||||
|
filterLongsByAuthors(relay, it, since)
|
||||||
|
},
|
||||||
|
).flatten()
|
||||||
|
}
|
||||||
|
}
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 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.longs.datasource.subassemblies
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.hashtag.HashtagTopNavPerRelayFilterSet
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
||||||
|
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
|
||||||
|
|
||||||
|
fun filterLongsByHashtag(
|
||||||
|
relay: NormalizedRelayUrl,
|
||||||
|
hashtags: Set<String>,
|
||||||
|
since: Long? = null,
|
||||||
|
): List<RelayBasedFilter> =
|
||||||
|
listOf(
|
||||||
|
RelayBasedFilter(
|
||||||
|
relay = relay,
|
||||||
|
filter =
|
||||||
|
Filter(
|
||||||
|
kinds = listOf(VideoNormalEvent.KIND, VideoHorizontalEvent.KIND),
|
||||||
|
tags = mapOf("t" to hashtags.toList()),
|
||||||
|
limit = 200,
|
||||||
|
since = since,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun filterLongsByHashtag(
|
||||||
|
hashtagSet: HashtagTopNavPerRelayFilterSet,
|
||||||
|
since: SincePerRelayMap?,
|
||||||
|
defaultSince: Long? = null,
|
||||||
|
): List<RelayBasedFilter> {
|
||||||
|
if (hashtagSet.set.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
return hashtagSet.set
|
||||||
|
.mapNotNull { relayHashSet ->
|
||||||
|
if (relayHashSet.value.hashtags.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
filterLongsByHashtag(
|
||||||
|
relay = relayHashSet.key,
|
||||||
|
hashtags = relayHashSet.value.hashtags,
|
||||||
|
since = since?.get(relayHashSet.key)?.time ?: defaultSince,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.flatten()
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* 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.longs.datasource.subassemblies
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.topNavFeeds.global.GlobalTopNavPerRelayFilterSet
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
|
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
||||||
|
import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
fun filterLongsGlobal(
|
||||||
|
relays: GlobalTopNavPerRelayFilterSet,
|
||||||
|
since: SincePerRelayMap?,
|
||||||
|
defaultSince: Long? = null,
|
||||||
|
): List<RelayBasedFilter> {
|
||||||
|
if (relays.set.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
return relays.set.map {
|
||||||
|
val since = since?.get(it.key)?.time ?: defaultSince ?: TimeUtils.oneWeekAgo()
|
||||||
|
RelayBasedFilter(
|
||||||
|
relay = it.key,
|
||||||
|
filter =
|
||||||
|
Filter(
|
||||||
|
kinds = listOf(VideoNormalEvent.KIND, VideoHorizontalEvent.KIND),
|
||||||
|
limit = 200,
|
||||||
|
since = since,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -124,7 +124,7 @@ class NotificationFeedFilter(
|
|||||||
|
|
||||||
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList().code
|
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList().code
|
||||||
|
|
||||||
fun followList(): TopFilter = account.settings.defaultDiscoveryFollowList.value
|
fun followList(): TopFilter = account.settings.defaultNotificationFollowList.value
|
||||||
|
|
||||||
fun TopFilter.isMuteList() = this is TopFilter.MuteList
|
fun TopFilter.isMuteList() = this is TopFilter.MuteList
|
||||||
|
|
||||||
|
|||||||
@@ -418,6 +418,7 @@
|
|||||||
<string name="polls">Polls</string>
|
<string name="polls">Polls</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="private_bookmarks">Private Bookmarks</string>
|
<string name="private_bookmarks">Private Bookmarks</string>
|
||||||
<string name="public_bookmarks">Public Bookmarks</string>
|
<string name="public_bookmarks">Public Bookmarks</string>
|
||||||
<string name="add_to_private_bookmarks">Add to Private Bookmarks</string>
|
<string name="add_to_private_bookmarks">Add to Private Bookmarks</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user