mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-07-13 12:09:22 +02:00
Adds a Marketplace tab to Discovery
This commit is contained in:
@ -9,6 +9,7 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
|||||||
import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
||||||
import com.vitorpamplona.quartz.events.ChannelMessageEvent
|
import com.vitorpamplona.quartz.events.ChannelMessageEvent
|
||||||
import com.vitorpamplona.quartz.events.ChannelMetadataEvent
|
import com.vitorpamplona.quartz.events.ChannelMetadataEvent
|
||||||
|
import com.vitorpamplona.quartz.events.ClassifiedsEvent
|
||||||
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
||||||
import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
|
import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
|
||||||
import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent
|
import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent
|
||||||
@ -42,6 +43,54 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") {
|
|||||||
job?.cancel()
|
job?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createMarketplaceFilter(): List<TypedFilter> {
|
||||||
|
val follows = account.liveDiscoveryFollowLists.value?.users?.toList()
|
||||||
|
val hashToLoad = account.liveDiscoveryFollowLists.value?.hashtags?.toList()
|
||||||
|
val geohashToLoad = account.liveDiscoveryFollowLists.value?.geotags?.toList()
|
||||||
|
|
||||||
|
return listOfNotNull(
|
||||||
|
TypedFilter(
|
||||||
|
types = setOf(FeedType.GLOBAL),
|
||||||
|
filter = JsonFilter(
|
||||||
|
authors = follows,
|
||||||
|
kinds = listOf(ClassifiedsEvent.kind),
|
||||||
|
limit = 300,
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.followList?.get(account.defaultDiscoveryFollowList.value)?.relayList
|
||||||
|
)
|
||||||
|
),
|
||||||
|
hashToLoad?.let {
|
||||||
|
TypedFilter(
|
||||||
|
types = setOf(FeedType.GLOBAL),
|
||||||
|
filter = JsonFilter(
|
||||||
|
kinds = listOf(ClassifiedsEvent.kind),
|
||||||
|
tags = mapOf(
|
||||||
|
"t" to it.map {
|
||||||
|
listOf(it, it.lowercase(), it.uppercase(), it.capitalize())
|
||||||
|
}.flatten()
|
||||||
|
),
|
||||||
|
limit = 300,
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.followList?.get(account.defaultDiscoveryFollowList.value)?.relayList
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
geohashToLoad?.let {
|
||||||
|
TypedFilter(
|
||||||
|
types = setOf(FeedType.GLOBAL),
|
||||||
|
filter = JsonFilter(
|
||||||
|
kinds = listOf(ClassifiedsEvent.kind),
|
||||||
|
tags = mapOf(
|
||||||
|
"g" to it.map {
|
||||||
|
listOf(it, it.lowercase(), it.uppercase(), it.capitalize())
|
||||||
|
}.flatten()
|
||||||
|
),
|
||||||
|
limit = 300,
|
||||||
|
since = latestEOSEs.users[account.userProfile()]?.followList?.get(account.defaultDiscoveryFollowList.value)?.relayList
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun createLiveStreamFilter(): List<TypedFilter> {
|
fun createLiveStreamFilter(): List<TypedFilter> {
|
||||||
val follows = account.liveDiscoveryFollowLists.value?.users?.toList()
|
val follows = account.liveDiscoveryFollowLists.value?.users?.toList()
|
||||||
|
|
||||||
@ -238,16 +287,19 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun updateChannelFilters() {
|
override fun updateChannelFilters() {
|
||||||
discoveryFeedChannel.typedFilters = createLiveStreamFilter().plus(createPublicChatFilter()).plus(
|
discoveryFeedChannel.typedFilters = createLiveStreamFilter()
|
||||||
listOfNotNull(
|
.plus(createPublicChatFilter())
|
||||||
createLiveStreamTagsFilter(),
|
.plus(createMarketplaceFilter())
|
||||||
createLiveStreamGeohashesFilter(),
|
.plus(
|
||||||
createCommunitiesFilter(),
|
listOfNotNull(
|
||||||
createPublicChatsTagsFilter(),
|
createLiveStreamTagsFilter(),
|
||||||
createCommunitiesTagsFilter(),
|
createLiveStreamGeohashesFilter(),
|
||||||
createCommunitiesGeohashesFilter(),
|
createCommunitiesFilter(),
|
||||||
createPublicChatsGeohashesFilter()
|
createCommunitiesTagsFilter(),
|
||||||
)
|
createCommunitiesGeohashesFilter(),
|
||||||
).ifEmpty { null }
|
createPublicChatsTagsFilter(),
|
||||||
|
createPublicChatsGeohashesFilter()
|
||||||
|
)
|
||||||
|
).ifEmpty { null }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.dal
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.quartz.events.ClassifiedsEvent
|
||||||
|
import com.vitorpamplona.quartz.events.MuteListEvent
|
||||||
|
import com.vitorpamplona.quartz.events.PeopleListEvent
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
open class DiscoverMarketplaceFeedFilter(
|
||||||
|
val account: Account
|
||||||
|
) : AdditiveFeedFilter<Note>() {
|
||||||
|
override fun feedKey(): String {
|
||||||
|
return account.userProfile().pubkeyHex + "-" + followList()
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun followList(): String {
|
||||||
|
return account.defaultDiscoveryFollowList.value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showHiddenKey(): Boolean {
|
||||||
|
return followList() == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
|
||||||
|
followList() == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun feed(): List<Note> {
|
||||||
|
val classifieds = LocalCache.addressables
|
||||||
|
.filter { it.value.event is ClassifiedsEvent }
|
||||||
|
.map { it.value }
|
||||||
|
|
||||||
|
val notes = innerApplyFilter(classifieds)
|
||||||
|
|
||||||
|
return sort(notes)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun applyFilter(collection: Set<Note>): Set<Note> {
|
||||||
|
return innerApplyFilter(collection)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
||||||
|
val now = TimeUtils.now()
|
||||||
|
val isGlobal = account.defaultDiscoveryFollowList.value == GLOBAL_FOLLOWS
|
||||||
|
val isHiddenList = showHiddenKey()
|
||||||
|
|
||||||
|
val followingKeySet = account.liveDiscoveryFollowLists.value?.users ?: emptySet()
|
||||||
|
val followingTagSet = account.liveDiscoveryFollowLists.value?.hashtags ?: emptySet()
|
||||||
|
val followingGeohashSet = account.liveDiscoveryFollowLists.value?.geotags ?: emptySet()
|
||||||
|
|
||||||
|
val activities = collection
|
||||||
|
.asSequence()
|
||||||
|
.filter {
|
||||||
|
it.event is ClassifiedsEvent &&
|
||||||
|
it.event?.hasTagWithContent("image") == true &&
|
||||||
|
it.event?.hasTagWithContent("price") == true &&
|
||||||
|
it.event?.hasTagWithContent("title") == true
|
||||||
|
}
|
||||||
|
.filter {
|
||||||
|
isGlobal || it.author?.pubkeyHex in followingKeySet || it.event?.isTaggedHashes(followingTagSet) == true || it.event?.isTaggedGeoHashes(followingGeohashSet) == true
|
||||||
|
}
|
||||||
|
.filter { isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true }
|
||||||
|
.filter { (it.createdAt() ?: 0) <= now }
|
||||||
|
.toSet()
|
||||||
|
|
||||||
|
return activities
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sort(collection: Set<Note>): List<Note> {
|
||||||
|
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,7 @@ import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverChatFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverChatFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverCommunityFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverCommunityFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverLiveFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverLiveFeedViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverMarketplaceFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
|
||||||
@ -61,6 +62,7 @@ fun AppNavigation(
|
|||||||
knownFeedViewModel: NostrChatroomListKnownFeedViewModel,
|
knownFeedViewModel: NostrChatroomListKnownFeedViewModel,
|
||||||
newFeedViewModel: NostrChatroomListNewFeedViewModel,
|
newFeedViewModel: NostrChatroomListNewFeedViewModel,
|
||||||
videoFeedViewModel: NostrVideoFeedViewModel,
|
videoFeedViewModel: NostrVideoFeedViewModel,
|
||||||
|
discoverMarketplaceFeedViewModel: NostrDiscoverMarketplaceFeedViewModel,
|
||||||
discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel,
|
discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel,
|
||||||
discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel,
|
discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel,
|
||||||
discoveryChatFeedViewModel: NostrDiscoverChatFeedViewModel,
|
discoveryChatFeedViewModel: NostrDiscoverChatFeedViewModel,
|
||||||
@ -137,6 +139,7 @@ fun AppNavigation(
|
|||||||
Route.Discover.let { route ->
|
Route.Discover.let { route ->
|
||||||
composable(route.route, route.arguments, content = {
|
composable(route.route, route.arguments, content = {
|
||||||
DiscoverScreen(
|
DiscoverScreen(
|
||||||
|
discoveryMarketplaceFeedViewModel = discoverMarketplaceFeedViewModel,
|
||||||
discoveryLiveFeedViewModel = discoveryLiveFeedViewModel,
|
discoveryLiveFeedViewModel = discoveryLiveFeedViewModel,
|
||||||
discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel,
|
discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel,
|
||||||
discoveryChatFeedViewModel = discoveryChatFeedViewModel,
|
discoveryChatFeedViewModel = discoveryChatFeedViewModel,
|
||||||
|
@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.note
|
|||||||
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@ -15,8 +16,10 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material3.Divider
|
import androidx.compose.material3.Divider
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
@ -39,6 +42,7 @@ import androidx.compose.ui.graphics.compositeOver
|
|||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
import androidx.lifecycle.distinctUntilChanged
|
||||||
@ -58,23 +62,28 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.EndedFlag
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LiveFlag
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LiveFlag
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.OfflineFlag
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.OfflineFlag
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ScheduledFlag
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ScheduledFlag
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
|
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
||||||
|
import com.vitorpamplona.quartz.events.ClassifiedsEvent
|
||||||
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
||||||
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
|
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
|
||||||
import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_ENDED
|
import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_ENDED
|
||||||
import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_LIVE
|
import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_LIVE
|
||||||
import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_PLANNED
|
import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_PLANNED
|
||||||
import com.vitorpamplona.quartz.events.Participant
|
import com.vitorpamplona.quartz.events.Participant
|
||||||
|
import com.vitorpamplona.quartz.events.Price
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
@ -95,7 +104,7 @@ fun ChannelCardCompose(
|
|||||||
) {
|
) {
|
||||||
val hasEvent by baseNote.live().hasEvent.observeAsState(baseNote.event != null)
|
val hasEvent by baseNote.live().hasEvent.observeAsState(baseNote.event != null)
|
||||||
|
|
||||||
Crossfade(targetState = hasEvent) {
|
Crossfade(targetState = hasEvent, label = "ChannelCardCompose") {
|
||||||
if (it) {
|
if (it) {
|
||||||
if (forceEventKind == null || baseNote.event?.kind() == forceEventKind) {
|
if (forceEventKind == null || baseNote.event?.kind() == forceEventKind) {
|
||||||
CheckHiddenChannelCardCompose(
|
CheckHiddenChannelCardCompose(
|
||||||
@ -155,7 +164,7 @@ fun CheckHiddenChannelCardCompose(
|
|||||||
note.isHiddenFor(it)
|
note.isHiddenFor(it)
|
||||||
}.distinctUntilChanged().observeAsState(accountViewModel.isNoteHidden(note))
|
}.distinctUntilChanged().observeAsState(accountViewModel.isNoteHidden(note))
|
||||||
|
|
||||||
Crossfade(targetState = isHidden) {
|
Crossfade(targetState = isHidden, label = "CheckHiddenChannelCardCompose") {
|
||||||
if (!it) {
|
if (!it) {
|
||||||
LoadedChannelCardCompose(
|
LoadedChannelCardCompose(
|
||||||
note,
|
note,
|
||||||
@ -195,7 +204,7 @@ fun LoadedChannelCardCompose(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Crossfade(targetState = state) {
|
Crossfade(targetState = state, label = "CheckHiddenChannelCardCompose") {
|
||||||
RenderChannelCardReportState(
|
RenderChannelCardReportState(
|
||||||
it,
|
it,
|
||||||
note,
|
note,
|
||||||
@ -220,7 +229,7 @@ fun RenderChannelCardReportState(
|
|||||||
) {
|
) {
|
||||||
var showReportedNote by remember { mutableStateOf(false) }
|
var showReportedNote by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Crossfade(targetState = !state.isAcceptable && !showReportedNote) { showHiddenNote ->
|
Crossfade(targetState = !state.isAcceptable && !showReportedNote, label = "CheckHiddenChannelCardCompose") { showHiddenNote ->
|
||||||
if (showHiddenNote) {
|
if (showHiddenNote) {
|
||||||
HiddenNote(
|
HiddenNote(
|
||||||
state.relevantReports,
|
state.relevantReports,
|
||||||
@ -334,6 +343,28 @@ fun InnerChannelCardWithReactions(
|
|||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
|
when (remember { baseNote.event }) {
|
||||||
|
is LiveActivitiesEvent -> {
|
||||||
|
InnerCardRow(baseNote, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
is CommunityDefinitionEvent -> {
|
||||||
|
InnerCardRow(baseNote, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
is ChannelCreateEvent -> {
|
||||||
|
InnerCardRow(baseNote, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
is ClassifiedsEvent -> {
|
||||||
|
InnerCardBox(baseNote, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InnerCardRow(
|
||||||
|
baseNote: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
Column(StdPadding) {
|
Column(StdPadding) {
|
||||||
SensitivityWarning(
|
SensitivityWarning(
|
||||||
@ -353,6 +384,22 @@ fun InnerChannelCardWithReactions(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InnerCardBox(
|
||||||
|
baseNote: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
|
Column(HalfPadding) {
|
||||||
|
SensitivityWarning(
|
||||||
|
note = baseNote,
|
||||||
|
accountViewModel = accountViewModel
|
||||||
|
) {
|
||||||
|
RenderClassifiedsThumb(baseNote, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RenderNoteRow(
|
private fun RenderNoteRow(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
@ -372,6 +419,116 @@ private fun RenderNoteRow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class ClassifiedsThumb(
|
||||||
|
val image: String?,
|
||||||
|
val title: String?,
|
||||||
|
val price: Price?
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RenderClassifiedsThumb(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
|
val noteEvent = baseNote.event as? ClassifiedsEvent ?: return
|
||||||
|
|
||||||
|
val card by baseNote.live().metadata.map {
|
||||||
|
val noteEvent = it.note.event as? ClassifiedsEvent
|
||||||
|
|
||||||
|
ClassifiedsThumb(
|
||||||
|
image = noteEvent?.image(),
|
||||||
|
title = noteEvent?.title(),
|
||||||
|
price = noteEvent?.price()
|
||||||
|
)
|
||||||
|
}.distinctUntilChanged().observeAsState(
|
||||||
|
ClassifiedsThumb(
|
||||||
|
image = noteEvent.image(),
|
||||||
|
title = noteEvent.title(),
|
||||||
|
price = noteEvent.price()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
RenderClassifiedsThumb(card, baseNote.author)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun RenderClassifiedsThumbPreview() {
|
||||||
|
Surface(Modifier.size(200.dp)) {
|
||||||
|
RenderClassifiedsThumb(
|
||||||
|
card = ClassifiedsThumb(
|
||||||
|
image = null,
|
||||||
|
title = "Like New",
|
||||||
|
price = Price("800000", "SATS", null)
|
||||||
|
),
|
||||||
|
author = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RenderClassifiedsThumb(card: ClassifiedsThumb, author: User?) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(1f),
|
||||||
|
contentAlignment = BottomStart
|
||||||
|
) {
|
||||||
|
card.image?.let {
|
||||||
|
AsyncImage(
|
||||||
|
model = it,
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
} ?: run {
|
||||||
|
author?.let {
|
||||||
|
DisplayAuthorBanner(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(Color.Black.copy(0.6f))
|
||||||
|
.padding(Size5dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
card.title?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
color = Color.White,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
card.price?.let {
|
||||||
|
val priceTag = remember(card) {
|
||||||
|
val newAmount = it.amount.toBigDecimalOrNull()?.let {
|
||||||
|
showAmountAxis(it)
|
||||||
|
} ?: it.amount
|
||||||
|
|
||||||
|
if (it.frequency != null && it.currency != null) {
|
||||||
|
"$newAmount ${it.currency}/${it.frequency}"
|
||||||
|
} else if (it.currency != null) {
|
||||||
|
"$newAmount ${it.currency}"
|
||||||
|
} else {
|
||||||
|
newAmount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = priceTag,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class LiveActivityCard(
|
data class LiveActivityCard(
|
||||||
val name: String,
|
val name: String,
|
||||||
|
@ -13,6 +13,8 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyGridState
|
||||||
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
@ -64,7 +66,9 @@ fun RefresheableView(
|
|||||||
|
|
||||||
val modifier = remember {
|
val modifier = remember {
|
||||||
if (enablePullRefresh) {
|
if (enablePullRefresh) {
|
||||||
Modifier.fillMaxSize().pullRefresh(pullRefreshState)
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.pullRefresh(pullRefreshState)
|
||||||
} else {
|
} else {
|
||||||
Modifier.fillMaxSize()
|
Modifier.fillMaxSize()
|
||||||
}
|
}
|
||||||
@ -115,6 +119,23 @@ fun SaveableFeedState(
|
|||||||
content(listState)
|
content(listState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SaveableGridFeedState(
|
||||||
|
viewModel: FeedViewModel,
|
||||||
|
scrollStateKey: String? = null,
|
||||||
|
content: @Composable (LazyGridState) -> Unit
|
||||||
|
) {
|
||||||
|
val gridState = if (scrollStateKey != null) {
|
||||||
|
rememberForeverLazyGridState(scrollStateKey)
|
||||||
|
} else {
|
||||||
|
rememberLazyGridState()
|
||||||
|
}
|
||||||
|
|
||||||
|
WatchScrollToTop(viewModel, gridState)
|
||||||
|
|
||||||
|
content(gridState)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RenderFeed(
|
private fun RenderFeed(
|
||||||
viewModel: FeedViewModel,
|
viewModel: FeedViewModel,
|
||||||
@ -174,6 +195,21 @@ private fun WatchScrollToTop(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun WatchScrollToTop(
|
||||||
|
viewModel: FeedViewModel,
|
||||||
|
listState: LazyGridState
|
||||||
|
) {
|
||||||
|
val scrollToTop by viewModel.scrollToTop.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
LaunchedEffect(scrollToTop) {
|
||||||
|
if (scrollToTop > 0 && viewModel.scrolltoTopPending) {
|
||||||
|
listState.scrollToItem(index = 0)
|
||||||
|
viewModel.sentToTop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalTime::class)
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalTime::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun FeedLoaded(
|
private fun FeedLoaded(
|
||||||
@ -190,7 +226,9 @@ private fun FeedLoaded(
|
|||||||
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item ->
|
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item ->
|
||||||
val (value, elapsed) = measureTimedValue {
|
val (value, elapsed) = measureTimedValue {
|
||||||
val defaultModifier = remember {
|
val defaultModifier = remember {
|
||||||
Modifier.fillMaxWidth().animateItemPlacement()
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.animateItemPlacement()
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(defaultModifier) {
|
Row(defaultModifier) {
|
||||||
|
@ -26,6 +26,7 @@ import com.vitorpamplona.amethyst.ui.dal.CommunityFeedFilter
|
|||||||
import com.vitorpamplona.amethyst.ui.dal.DiscoverChatFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.DiscoverChatFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.DiscoverCommunityFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.DiscoverCommunityFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.DiscoverLiveFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.DiscoverLiveFeedFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.dal.DiscoverMarketplaceFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.GeoHashFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.GeoHashFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter
|
||||||
@ -72,6 +73,16 @@ class NostrVideoFeedViewModel(val account: Account) : FeedViewModel(VideoFeedFil
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NostrDiscoverMarketplaceFeedViewModel(val account: Account) : FeedViewModel(
|
||||||
|
DiscoverMarketplaceFeedFilter(account)
|
||||||
|
) {
|
||||||
|
class Factory(val account: Account) : ViewModelProvider.Factory {
|
||||||
|
override fun <NostrDiscoverMarketplaceFeedViewModel : ViewModel> create(modelClass: Class<NostrDiscoverMarketplaceFeedViewModel>): NostrDiscoverMarketplaceFeedViewModel {
|
||||||
|
return NostrDiscoverMarketplaceFeedViewModel(account) as NostrDiscoverMarketplaceFeedViewModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class NostrDiscoverLiveFeedViewModel(val account: Account) : FeedViewModel(DiscoverLiveFeedFilter(account)) {
|
class NostrDiscoverLiveFeedViewModel(val account: Account) : FeedViewModel(DiscoverLiveFeedFilter(account)) {
|
||||||
class Factory(val account: Account) : ViewModelProvider.Factory {
|
class Factory(val account: Account) : ViewModelProvider.Factory {
|
||||||
override fun <NostrDiscoverLiveFeedViewModel : ViewModel> create(modelClass: Class<NostrDiscoverLiveFeedViewModel>): NostrDiscoverLiveFeedViewModel {
|
override fun <NostrDiscoverLiveFeedViewModel : ViewModel> create(modelClass: Class<NostrDiscoverLiveFeedViewModel>): NostrDiscoverLiveFeedViewModel {
|
||||||
|
@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.screen
|
|||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyGridState
|
||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -22,6 +23,7 @@ object ScrollStateKeys {
|
|||||||
val HOME_FOLLOWS = Route.Home.base + "Follows"
|
val HOME_FOLLOWS = Route.Home.base + "Follows"
|
||||||
val HOME_REPLIES = Route.Home.base + "FollowsReplies"
|
val HOME_REPLIES = Route.Home.base + "FollowsReplies"
|
||||||
|
|
||||||
|
val DISCOVER_MARKETPLACE = Route.Home.base + "Marketplace"
|
||||||
val DISCOVER_LIVE = Route.Home.base + "Live"
|
val DISCOVER_LIVE = Route.Home.base + "Live"
|
||||||
val DISCOVER_COMMUNITY = Route.Home.base + "Communities"
|
val DISCOVER_COMMUNITY = Route.Home.base + "Communities"
|
||||||
val DISCOVER_CHATS = Route.Home.base + "Chats"
|
val DISCOVER_CHATS = Route.Home.base + "Chats"
|
||||||
@ -32,6 +34,31 @@ object PagerStateKeys {
|
|||||||
const val DISCOVER_SCREEN = "PagerDiscover"
|
const val DISCOVER_SCREEN = "PagerDiscover"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberForeverLazyGridState(
|
||||||
|
key: String,
|
||||||
|
initialFirstVisibleItemIndex: Int = 0,
|
||||||
|
initialFirstVisibleItemScrollOffset: Int = 0
|
||||||
|
): LazyGridState {
|
||||||
|
val scrollState = rememberSaveable(saver = LazyGridState.Saver) {
|
||||||
|
val savedValue = savedScrollStates[key]
|
||||||
|
val savedIndex = savedValue?.index ?: initialFirstVisibleItemIndex
|
||||||
|
val savedOffset = savedValue?.scrollOffsetFraction ?: initialFirstVisibleItemScrollOffset.toFloat()
|
||||||
|
LazyGridState(
|
||||||
|
savedIndex,
|
||||||
|
savedOffset.roundToInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
DisposableEffect(scrollState) {
|
||||||
|
onDispose {
|
||||||
|
val lastIndex = scrollState.firstVisibleItemIndex
|
||||||
|
val lastOffset = scrollState.firstVisibleItemScrollOffset
|
||||||
|
savedScrollStates[key] = ScrollState(lastIndex, lastOffset.toFloat())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scrollState
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberForeverLazyListState(
|
fun rememberForeverLazyListState(
|
||||||
key: String,
|
key: String,
|
||||||
|
@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
|||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.gestures.ScrollableState
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
@ -18,8 +19,8 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
|||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ScrollableTabRow
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
import androidx.compose.material3.TabRow
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
@ -47,14 +48,17 @@ import com.vitorpamplona.amethyst.ui.screen.LoadingFeed
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverChatFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverChatFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverCommunityFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverCommunityFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverLiveFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverLiveFeedViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverMarketplaceFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.PagerStateKeys
|
import com.vitorpamplona.amethyst.ui.screen.PagerStateKeys
|
||||||
import com.vitorpamplona.amethyst.ui.screen.RefresheableView
|
import com.vitorpamplona.amethyst.ui.screen.RefresheableView
|
||||||
import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState
|
import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.SaveableGridFeedState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys
|
import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys
|
||||||
import com.vitorpamplona.amethyst.ui.screen.rememberForeverPagerState
|
import com.vitorpamplona.amethyst.ui.screen.rememberForeverPagerState
|
||||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
|
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
|
||||||
import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
||||||
|
import com.vitorpamplona.quartz.events.ClassifiedsEvent
|
||||||
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
||||||
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
|
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
@ -64,6 +68,7 @@ import kotlinx.coroutines.launch
|
|||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun DiscoverScreen(
|
fun DiscoverScreen(
|
||||||
|
discoveryMarketplaceFeedViewModel: NostrDiscoverMarketplaceFeedViewModel,
|
||||||
discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel,
|
discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel,
|
||||||
discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel,
|
discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel,
|
||||||
discoveryChatFeedViewModel: NostrDiscoverChatFeedViewModel,
|
discoveryChatFeedViewModel: NostrDiscoverChatFeedViewModel,
|
||||||
@ -75,6 +80,7 @@ fun DiscoverScreen(
|
|||||||
val tabs by remember(discoveryLiveFeedViewModel, discoveryCommunityFeedViewModel, discoveryChatFeedViewModel) {
|
val tabs by remember(discoveryLiveFeedViewModel, discoveryCommunityFeedViewModel, discoveryChatFeedViewModel) {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
listOf(
|
listOf(
|
||||||
|
TabItem(R.string.discover_marketplace, discoveryMarketplaceFeedViewModel, Route.Discover.base + "Marketplace", ScrollStateKeys.DISCOVER_MARKETPLACE, ClassifiedsEvent.kind),
|
||||||
TabItem(R.string.discover_live, discoveryLiveFeedViewModel, Route.Discover.base + "Live", ScrollStateKeys.DISCOVER_LIVE, LiveActivitiesEvent.kind),
|
TabItem(R.string.discover_live, discoveryLiveFeedViewModel, Route.Discover.base + "Live", ScrollStateKeys.DISCOVER_LIVE, LiveActivitiesEvent.kind),
|
||||||
TabItem(R.string.discover_community, discoveryCommunityFeedViewModel, Route.Discover.base + "Community", ScrollStateKeys.DISCOVER_COMMUNITY, CommunityDefinitionEvent.kind),
|
TabItem(R.string.discover_community, discoveryCommunityFeedViewModel, Route.Discover.base + "Community", ScrollStateKeys.DISCOVER_COMMUNITY, CommunityDefinitionEvent.kind),
|
||||||
TabItem(R.string.discover_chat, discoveryChatFeedViewModel, Route.Discover.base + "Chats", ScrollStateKeys.DISCOVER_CHATS, ChannelCreateEvent.kind)
|
TabItem(R.string.discover_chat, discoveryChatFeedViewModel, Route.Discover.base + "Chats", ScrollStateKeys.DISCOVER_CHATS, ChannelCreateEvent.kind)
|
||||||
@ -85,6 +91,7 @@ fun DiscoverScreen(
|
|||||||
val pagerState = rememberForeverPagerState(key = PagerStateKeys.DISCOVER_SCREEN) { tabs.size }
|
val pagerState = rememberForeverPagerState(key = PagerStateKeys.DISCOVER_SCREEN) { tabs.size }
|
||||||
|
|
||||||
WatchAccountForDiscoveryScreen(
|
WatchAccountForDiscoveryScreen(
|
||||||
|
discoverMarketplaceFeedViewModel = discoveryMarketplaceFeedViewModel,
|
||||||
discoveryLiveFeedViewModel = discoveryLiveFeedViewModel,
|
discoveryLiveFeedViewModel = discoveryLiveFeedViewModel,
|
||||||
discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel,
|
discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel,
|
||||||
discoveryChatFeedViewModel = discoveryChatFeedViewModel,
|
discoveryChatFeedViewModel = discoveryChatFeedViewModel,
|
||||||
@ -122,11 +129,12 @@ private fun DiscoverPages(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
TabRow(
|
ScrollableTabRow(
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
containerColor = MaterialTheme.colorScheme.background,
|
||||||
contentColor = MaterialTheme.colorScheme.onBackground,
|
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||||
selectedTabIndex = pagerState.currentPage,
|
selectedTabIndex = pagerState.currentPage,
|
||||||
modifier = TabRowHeight
|
modifier = TabRowHeight,
|
||||||
|
edgePadding = 8.dp
|
||||||
) {
|
) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
@ -145,15 +153,28 @@ private fun DiscoverPages(
|
|||||||
|
|
||||||
HorizontalPager(state = pagerState) { page ->
|
HorizontalPager(state = pagerState) { page ->
|
||||||
RefresheableView(tabs[page].viewModel, true) {
|
RefresheableView(tabs[page].viewModel, true) {
|
||||||
SaveableFeedState(tabs[page].viewModel, scrollStateKey = tabs[page].scrollStateKey) { listState ->
|
if (tabs[page].viewModel is NostrDiscoverMarketplaceFeedViewModel) {
|
||||||
RenderDiscoverFeed(
|
SaveableGridFeedState(tabs[page].viewModel, scrollStateKey = tabs[page].scrollStateKey) { listState ->
|
||||||
viewModel = tabs[page].viewModel,
|
RenderDiscoverFeed(
|
||||||
routeForLastRead = tabs[page].routeForLastRead,
|
viewModel = tabs[page].viewModel,
|
||||||
forceEventKind = tabs[page].forceEventKind,
|
routeForLastRead = tabs[page].routeForLastRead,
|
||||||
listState = listState,
|
forceEventKind = tabs[page].forceEventKind,
|
||||||
accountViewModel = accountViewModel,
|
listState = listState,
|
||||||
nav = nav
|
accountViewModel = accountViewModel,
|
||||||
)
|
nav = nav
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SaveableFeedState(tabs[page].viewModel, scrollStateKey = tabs[page].scrollStateKey) { listState ->
|
||||||
|
RenderDiscoverFeed(
|
||||||
|
viewModel = tabs[page].viewModel,
|
||||||
|
routeForLastRead = tabs[page].routeForLastRead,
|
||||||
|
forceEventKind = tabs[page].forceEventKind,
|
||||||
|
listState = listState,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,7 +185,7 @@ private fun RenderDiscoverFeed(
|
|||||||
viewModel: FeedViewModel,
|
viewModel: FeedViewModel,
|
||||||
routeForLastRead: String?,
|
routeForLastRead: String?,
|
||||||
forceEventKind: Int?,
|
forceEventKind: Int?,
|
||||||
listState: LazyListState,
|
listState: ScrollableState,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
@ -189,14 +210,25 @@ private fun RenderDiscoverFeed(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is FeedState.Loaded -> {
|
is FeedState.Loaded -> {
|
||||||
DiscoverFeedLoaded(
|
if (listState is LazyGridState) {
|
||||||
state,
|
DiscoverFeedColumnsLoaded(
|
||||||
routeForLastRead,
|
state,
|
||||||
listState,
|
routeForLastRead,
|
||||||
forceEventKind,
|
listState,
|
||||||
accountViewModel,
|
forceEventKind,
|
||||||
nav
|
accountViewModel,
|
||||||
)
|
nav
|
||||||
|
)
|
||||||
|
} else if (listState is LazyListState) {
|
||||||
|
DiscoverFeedLoaded(
|
||||||
|
state,
|
||||||
|
routeForLastRead,
|
||||||
|
listState,
|
||||||
|
forceEventKind,
|
||||||
|
accountViewModel,
|
||||||
|
nav
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is FeedState.Loading -> {
|
is FeedState.Loading -> {
|
||||||
@ -208,6 +240,7 @@ private fun RenderDiscoverFeed(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WatchAccountForDiscoveryScreen(
|
fun WatchAccountForDiscoveryScreen(
|
||||||
|
discoverMarketplaceFeedViewModel: NostrDiscoverMarketplaceFeedViewModel,
|
||||||
discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel,
|
discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel,
|
||||||
discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel,
|
discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel,
|
||||||
discoveryChatFeedViewModel: NostrDiscoverChatFeedViewModel,
|
discoveryChatFeedViewModel: NostrDiscoverChatFeedViewModel,
|
||||||
@ -217,6 +250,7 @@ fun WatchAccountForDiscoveryScreen(
|
|||||||
|
|
||||||
LaunchedEffect(accountViewModel, listState) {
|
LaunchedEffect(accountViewModel, listState) {
|
||||||
NostrDiscoveryDataSource.resetFilters()
|
NostrDiscoveryDataSource.resetFilters()
|
||||||
|
discoverMarketplaceFeedViewModel.checkKeysInvalidateDataAndSendToTop()
|
||||||
discoveryLiveFeedViewModel.checkKeysInvalidateDataAndSendToTop()
|
discoveryLiveFeedViewModel.checkKeysInvalidateDataAndSendToTop()
|
||||||
discoveryCommunityFeedViewModel.checkKeysInvalidateDataAndSendToTop()
|
discoveryCommunityFeedViewModel.checkKeysInvalidateDataAndSendToTop()
|
||||||
discoveryChatFeedViewModel.checkKeysInvalidateDataAndSendToTop()
|
discoveryChatFeedViewModel.checkKeysInvalidateDataAndSendToTop()
|
||||||
@ -260,7 +294,7 @@ private fun DiscoverFeedLoaded(
|
|||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun DiscoverFeedTwoColumnsLoaded(
|
private fun DiscoverFeedColumnsLoaded(
|
||||||
state: FeedState.Loaded,
|
state: FeedState.Loaded,
|
||||||
routeForLastRead: String?,
|
routeForLastRead: String?,
|
||||||
listState: LazyGridState,
|
listState: LazyGridState,
|
||||||
|
@ -82,6 +82,7 @@ import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverChatFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverChatFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverCommunityFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverCommunityFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverLiveFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverLiveFeedViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverMarketplaceFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
|
||||||
@ -169,6 +170,11 @@ fun MainScreen(
|
|||||||
factory = NostrVideoFeedViewModel.Factory(accountViewModel.account)
|
factory = NostrVideoFeedViewModel.Factory(accountViewModel.account)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val discoverMarketplaceFeedViewModel: NostrDiscoverMarketplaceFeedViewModel = viewModel(
|
||||||
|
key = "NostrDiscoveryMarketplaceFeedViewModel",
|
||||||
|
factory = NostrDiscoverMarketplaceFeedViewModel.Factory(accountViewModel.account)
|
||||||
|
)
|
||||||
|
|
||||||
val discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel = viewModel(
|
val discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel = viewModel(
|
||||||
key = "NostrDiscoveryLiveFeedViewModel",
|
key = "NostrDiscoveryLiveFeedViewModel",
|
||||||
factory = NostrDiscoverLiveFeedViewModel.Factory(accountViewModel.account)
|
factory = NostrDiscoverLiveFeedViewModel.Factory(accountViewModel.account)
|
||||||
@ -364,6 +370,7 @@ fun MainScreen(
|
|||||||
knownFeedViewModel = knownFeedViewModel,
|
knownFeedViewModel = knownFeedViewModel,
|
||||||
newFeedViewModel = newFeedViewModel,
|
newFeedViewModel = newFeedViewModel,
|
||||||
videoFeedViewModel = videoFeedViewModel,
|
videoFeedViewModel = videoFeedViewModel,
|
||||||
|
discoverMarketplaceFeedViewModel = discoverMarketplaceFeedViewModel,
|
||||||
discoveryLiveFeedViewModel = discoveryLiveFeedViewModel,
|
discoveryLiveFeedViewModel = discoveryLiveFeedViewModel,
|
||||||
discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel,
|
discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel,
|
||||||
discoveryChatFeedViewModel = discoveryChatFeedViewModel,
|
discoveryChatFeedViewModel = discoveryChatFeedViewModel,
|
||||||
|
@ -489,6 +489,7 @@
|
|||||||
|
|
||||||
<string name="relay_setup">Relays</string>
|
<string name="relay_setup">Relays</string>
|
||||||
|
|
||||||
|
<string name="discover_marketplace">Marketplace</string>
|
||||||
<string name="discover_live">Live</string>
|
<string name="discover_live">Live</string>
|
||||||
<string name="discover_community">Community</string>
|
<string name="discover_community">Community</string>
|
||||||
<string name="discover_chat">Chats</string>
|
<string name="discover_chat">Chats</string>
|
||||||
|
@ -64,7 +64,9 @@ open class Event(
|
|||||||
|
|
||||||
override fun toJson(): String = mapper.writeValueAsString(toJsonObject())
|
override fun toJson(): String = mapper.writeValueAsString(toJsonObject())
|
||||||
|
|
||||||
override fun hasAnyTaggedUser() = tags.any { it.size > 1 && it[0] == "p" }
|
override fun hasAnyTaggedUser() = hasTagWithContent("p")
|
||||||
|
|
||||||
|
override fun hasTagWithContent(tagName: String) = tags.any { it.size > 1 && it[0] == tagName }
|
||||||
|
|
||||||
override fun taggedUsers() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
|
override fun taggedUsers() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
|
||||||
override fun taggedEvents() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
|
override fun taggedEvents() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
|
||||||
|
@ -68,6 +68,7 @@ interface EventInterface {
|
|||||||
fun zapraiserAmount(): Long?
|
fun zapraiserAmount(): Long?
|
||||||
|
|
||||||
fun hasAnyTaggedUser(): Boolean
|
fun hasAnyTaggedUser(): Boolean
|
||||||
|
fun hasTagWithContent(tagName: String): Boolean
|
||||||
|
|
||||||
fun taggedAddresses(): List<ATag>
|
fun taggedAddresses(): List<ATag>
|
||||||
fun taggedUsers(): List<HexKey>
|
fun taggedUsers(): List<HexKey>
|
||||||
|
Reference in New Issue
Block a user