From a42762de53b3e00abca6d81852e14981a785958f Mon Sep 17 00:00:00 2001 From: Believethehype <1097224+believethehype@users.noreply.github.com> Date: Fri, 28 Jun 2024 19:14:36 +0200 Subject: [PATCH] alternative attempt.. --- .../ui/dal/UserProfileGalleryFeedFilter.kt | 52 +++++++ .../amethyst/ui/screen/FeedViewModel.kt | 11 ++ .../ui/screen/loggedIn/ProfileGallery.kt | 134 +++++++++++++++++- .../ui/screen/loggedIn/ProfileScreen.kt | 47 +++++- 4 files changed, 239 insertions(+), 5 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileGalleryFeedFilter.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileGalleryFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileGalleryFeedFilter.kt new file mode 100644 index 000000000..6c5e5a86e --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/UserProfileGalleryFeedFilter.kt @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2024 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.dal + +import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.model.User + +class UserProfileGalleryFeedFilter(val user: User, val account: Account) : FeedFilter() { + override fun feedKey(): String { + return account.userProfile().pubkeyHex + "-" + user.pubkeyHex + } + + override fun feed(): List { + val notes = + user.latestGalleryList + ?.taggedEvents() + ?.mapNotNull { LocalCache.checkGetOrCreateNote(it) } + ?.toSet() + ?: emptySet() + + val addresses = + user.latestGalleryList + ?.taggedAddresses() + ?.map { LocalCache.getOrCreateAddressableNote(it) } + ?.toSet() + ?: emptySet() + + return (notes + addresses) + .filter { account.isAcceptable(it) } + .sortedWith(DefaultFeedOrder) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt index 422bd15a2..04169e499 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt @@ -57,6 +57,7 @@ import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileAppRecommendationsFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileBookmarksFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileConversationsFeedFilter +import com.vitorpamplona.amethyst.ui.dal.UserProfileGalleryFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileNewThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter import com.vitorpamplona.amethyst.ui.dal.VideoFeedFilter @@ -214,6 +215,16 @@ class NostrUserProfileReportFeedViewModel(val user: User) : } } +class NostrUserProfileGalleryFeedViewModel(val user: User, val account: Account) : + FeedViewModel(UserProfileGalleryFeedFilter(user, account)) { + class Factory(val user: User, val account: Account) : ViewModelProvider.Factory { + override fun create(modelClass: Class): NostrUserProfileGalleryFeedViewModel { + return NostrUserProfileGalleryFeedViewModel(user, account) + as NostrUserProfileGalleryFeedViewModel + } + } +} + class NostrUserProfileBookmarksFeedViewModel(val user: User, val account: Account) : FeedViewModel(UserProfileBookmarksFeedFilter(user, account)) { class Factory(val user: User, val account: Account) : ViewModelProvider.Factory { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileGallery.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileGallery.kt index 08204061f..3f3529a04 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileGallery.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileGallery.kt @@ -20,6 +20,8 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn +import androidx.compose.animation.core.tween +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -30,6 +32,10 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +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.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -37,6 +43,7 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment.Companion.BottomStart import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -46,28 +53,149 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.map import coil.compose.AsyncImage import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport import com.vitorpamplona.amethyst.ui.note.ClickableNote import com.vitorpamplona.amethyst.ui.note.LongPressToQuickAction +import com.vitorpamplona.amethyst.ui.note.NormalChannelCard import com.vitorpamplona.amethyst.ui.note.WatchAuthor import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent import com.vitorpamplona.amethyst.ui.note.calculateBackgroundColor import com.vitorpamplona.amethyst.ui.note.elements.BannerImage +import com.vitorpamplona.amethyst.ui.screen.FeedEmpty +import com.vitorpamplona.amethyst.ui.screen.FeedError +import com.vitorpamplona.amethyst.ui.screen.FeedState +import com.vitorpamplona.amethyst.ui.screen.FeedViewModel +import com.vitorpamplona.amethyst.ui.screen.LoadingFeed +import com.vitorpamplona.amethyst.ui.theme.DividerThickness +import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.HalfPadding import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.amethyst.ui.theme.Size5dp import com.vitorpamplona.quartz.events.GalleryListEvent +@Composable +private fun RenderGalleryFeed( + viewModel: FeedViewModel, + routeForLastRead: String?, + forceEventKind: Int?, + listState: LazyListState, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + val feedState by viewModel.feedContent.collectAsStateWithLifecycle() + + CrossfadeIfEnabled( + targetState = feedState, + animationSpec = tween(durationMillis = 100), + label = "RenderDiscoverFeed", + accountViewModel = accountViewModel, + ) { state -> + when (state) { + is FeedState.Empty -> { + FeedEmpty { viewModel.invalidateData() } + } + is FeedState.FeedError -> { + FeedError(state.errorMessage) { viewModel.invalidateData() } + } + is FeedState.Loaded -> { + GalleryFeedLoaded( + state, + routeForLastRead, + listState, + forceEventKind, + accountViewModel, + nav, + ) + } + is FeedState.Loading -> { + LoadingFeed() + } + } + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun GalleryFeedLoaded( + state: FeedState.Loaded, + routeForLastRead: String?, + listState: LazyListState, + forceEventKind: Int?, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + LazyColumn( + contentPadding = FeedPadding, + state = listState, + ) { + itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> + val defaultModifier = remember { Modifier.fillMaxWidth().animateItemPlacement() } + + Row(defaultModifier) { + GalleryCardCompose( + baseNote = item, + routeForLastRead = routeForLastRead, + modifier = Modifier.fillMaxWidth(), + forceEventKind = forceEventKind, + accountViewModel = accountViewModel, + nav = nav, + ) + } + + HorizontalDivider( + thickness = DividerThickness, + ) + } + } +} + +@Composable +fun GalleryCardCompose( + baseNote: Note, + routeForLastRead: String? = null, + modifier: Modifier = Modifier, + parentBackgroundColor: MutableState? = null, + forceEventKind: Int?, + isHiddenFeed: Boolean = false, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + WatchNoteEvent(baseNote = baseNote, accountViewModel = accountViewModel) { + if (forceEventKind == null || baseNote.event?.kind() == forceEventKind) { + CheckHiddenFeedWatchBlockAndReport( + note = baseNote, + modifier = modifier, + ignoreAllBlocksAndReports = isHiddenFeed, + showHiddenWarning = false, + accountViewModel = accountViewModel, + nav = nav, + ) { canPreview -> + NormalChannelCard( + baseNote = baseNote, + routeForLastRead = routeForLastRead, + modifier = modifier, + parentBackgroundColor = parentBackgroundColor, + accountViewModel = accountViewModel, + nav = nav, + ) + } + } + } +} + // TODO This is to large parts from the ChannelCardCompose -// Why does it not be in a grid, like the marketplace + +/*@OptIn(ExperimentalFoundationApi::class) @Composable fun ProfileGallery( - baseNotes: List, + baseNote: List, modifier: Modifier = Modifier, parentBackgroundColor: MutableState? = null, isHiddenFeed: Boolean = false, @@ -103,7 +231,7 @@ fun ProfileGallery( } } } - +*/ @Composable fun GalleryCard( baseNote: Note, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index 735444d82..59e1c16b6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -148,6 +148,7 @@ import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileBookmarksFeedViewMod import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileConversationsFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileFollowersUserFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileFollowsUserFeedViewModel +import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileGalleryFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileNewThreadsFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileReportFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileZapsFeedViewModel @@ -294,6 +295,16 @@ fun PrepareViewModels( ), ) + val galleryFeedViewModel: NostrUserProfileGalleryFeedViewModel = + viewModel( + key = baseUser.pubkeyHex + "UserGalleryFeedViewModel", + factory = + NostrUserProfileGalleryFeedViewModel.Factory( + baseUser, + accountViewModel.account, + ), + ) + val reportsFeedViewModel: NostrUserProfileReportFeedViewModel = viewModel( key = baseUser.pubkeyHex + "UserProfileReportFeedViewModel", @@ -312,6 +323,7 @@ fun PrepareViewModels( appRecommendations, zapFeedViewModel, bookmarksFeedViewModel, + galleryFeedViewModel, reportsFeedViewModel, accountViewModel = accountViewModel, nav = nav, @@ -328,6 +340,7 @@ fun ProfileScreen( appRecommendations: NostrUserAppRecommendationsFeedViewModel, zapFeedViewModel: NostrUserProfileZapsFeedViewModel, bookmarksFeedViewModel: NostrUserProfileBookmarksFeedViewModel, + galleryFeedViewModel: NostrUserProfileGalleryFeedViewModel, reportsFeedViewModel: NostrUserProfileReportFeedViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit, @@ -372,6 +385,7 @@ fun ProfileScreen( followersFeedViewModel, zapFeedViewModel, bookmarksFeedViewModel, + galleryFeedViewModel, reportsFeedViewModel, accountViewModel, nav, @@ -388,6 +402,7 @@ private fun RenderSurface( followersFeedViewModel: NostrUserProfileFollowersUserFeedViewModel, zapFeedViewModel: NostrUserProfileZapsFeedViewModel, bookmarksFeedViewModel: NostrUserProfileBookmarksFeedViewModel, + galleryFeedViewModel: NostrUserProfileGalleryFeedViewModel, reportsFeedViewModel: NostrUserProfileReportFeedViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit, @@ -448,6 +463,7 @@ private fun RenderSurface( followersFeedViewModel, zapFeedViewModel, bookmarksFeedViewModel, + galleryFeedViewModel, reportsFeedViewModel, accountViewModel, nav, @@ -470,6 +486,7 @@ private fun RenderScreen( followersFeedViewModel: NostrUserProfileFollowersUserFeedViewModel, zapFeedViewModel: NostrUserProfileZapsFeedViewModel, bookmarksFeedViewModel: NostrUserProfileBookmarksFeedViewModel, + galleryFeedViewModel: NostrUserProfileGalleryFeedViewModel, reportsFeedViewModel: NostrUserProfileReportFeedViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit, @@ -501,6 +518,7 @@ private fun RenderScreen( followersFeedViewModel, zapFeedViewModel, bookmarksFeedViewModel, + galleryFeedViewModel, reportsFeedViewModel, accountViewModel, nav, @@ -519,6 +537,7 @@ private fun CreateAndRenderPages( followersFeedViewModel: NostrUserProfileFollowersUserFeedViewModel, zapFeedViewModel: NostrUserProfileZapsFeedViewModel, bookmarksFeedViewModel: NostrUserProfileBookmarksFeedViewModel, + galleryFeedViewModel: NostrUserProfileGalleryFeedViewModel, reportsFeedViewModel: NostrUserProfileReportFeedViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit, @@ -533,7 +552,7 @@ private fun CreateAndRenderPages( when (page) { 0 -> TabNotesNewThreads(threadsViewModel, accountViewModel, nav) 1 -> TabNotesConversations(repliesViewModel, accountViewModel, nav) - 2 -> Gallery(baseUser, followsFeedViewModel, accountViewModel, nav) + 2 -> TabGallery(galleryFeedViewModel, accountViewModel, nav) 3 -> TabFollows(baseUser, followsFeedViewModel, accountViewModel, nav) 4 -> TabFollowers(baseUser, followersFeedViewModel, accountViewModel, nav) 5 -> TabReceivedZaps(baseUser, zapFeedViewModel, accountViewModel, nav) @@ -1537,7 +1556,31 @@ fun TabNotesConversations( } } +@OptIn(ExperimentalFoundationApi::class) @Composable +fun TabGallery( + feedViewModel: NostrUserProfileGalleryFeedViewModel, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + LaunchedEffect(Unit) { feedViewModel.invalidateData() } + + Column(Modifier.fillMaxHeight()) { + Column( + modifier = Modifier.padding(vertical = 0.dp), + ) { + RefresheableFeedView( + feedViewModel, + null, + enablePullRefresh = false, + accountViewModel = accountViewModel, + nav = nav, + ) + } + } +} + +/*@Composable fun Gallery( baseUser: User, feedViewModel: UserFeedViewModel, @@ -1579,7 +1622,7 @@ fun Gallery( } } } -} +} */ @Composable fun TabFollowedTags(