diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/ProfileScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/ProfileScreen.kt index 90565778b..2f6e0c037 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/ProfileScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/ProfileScreen.kt @@ -87,8 +87,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.mutual.TabMutualCon import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.mutual.dal.UserProfileMutualFeedViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.newthreads.TabNotesNewThreads import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.newthreads.dal.UserProfileNewThreadsFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.pinnedNotes.PinnedNotesTabHeader -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.pinnedNotes.TabPinnedNotes import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.pinnedNotes.dal.UserProfilePinnedNotesFeedViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.relays.RelaysTabHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.relays.TabRelays @@ -408,7 +406,7 @@ private fun RenderScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val pagerState = rememberPagerState { 12 } + val pagerState = rememberPagerState { 11 } Column { ProfileHeader(baseUser, appRecommendations, externalIdentities, nav, accountViewModel) @@ -430,7 +428,6 @@ private fun RenderScreen( followersFeedViewModel, zapFeedViewModel, bookmarksFeedViewModel, - pinnedNotesFeedViewModel, galleryFeedViewModel, reportsFeedViewModel, accountViewModel, @@ -485,7 +482,7 @@ private fun CreateAndRenderPages( ) when (page) { - 0 -> TabNotesNewThreads(threadsViewModel, accountViewModel, nav) + 0 -> TabNotesNewThreads(threadsViewModel, pinnedNotesFeedViewModel, accountViewModel, nav) 1 -> TabNotesConversations(repliesViewModel, accountViewModel, nav) 2 -> TabMutualConversations(mutualViewModel, accountViewModel, nav) 3 -> TabGallery(galleryFeedViewModel, accountViewModel, nav) @@ -493,10 +490,9 @@ private fun CreateAndRenderPages( 5 -> TabFollowers(followersFeedViewModel, accountViewModel, nav) 6 -> TabReceivedZaps(baseUser, zapFeedViewModel, accountViewModel, nav) 7 -> TabBookmarks(bookmarksFeedViewModel, accountViewModel, nav) - 8 -> TabPinnedNotes(pinnedNotesFeedViewModel, accountViewModel, nav) - 9 -> TabFollowedTags(baseUser, accountViewModel, nav) - 10 -> TabReports(baseUser, reportsFeedViewModel, accountViewModel, nav) - 11 -> TabRelays(baseUser, accountViewModel, nav) + 8 -> TabFollowedTags(baseUser, accountViewModel, nav) + 9 -> TabReports(baseUser, reportsFeedViewModel, accountViewModel, nav) + 10 -> TabRelays(baseUser, accountViewModel, nav) } } @@ -526,7 +522,6 @@ private fun CreateAndRenderTabs( followersFeedViewModel: UserProfileFollowersUserFeedViewModel, zapFeedViewModel: UserProfileZapsViewModel, bookmarksFeedViewModel: UserProfileBookmarksFeedViewModel, - pinnedNotesFeedViewModel: UserProfilePinnedNotesFeedViewModel, galleryFeedViewModel: UserProfileGalleryFeedViewModel, reportsFeedViewModel: UserProfileReportFeedViewModel, accountViewModel: AccountViewModel, @@ -543,7 +538,6 @@ private fun CreateAndRenderTabs( { FollowersTabHeader(baseUser, followersFeedViewModel, accountViewModel) }, { ZapTabHeader(zapFeedViewModel, accountViewModel) }, { BookmarkTabHeader(baseUser, accountViewModel) }, - { PinnedNotesTabHeader(baseUser, accountViewModel) }, { FollowedTagsTabHeader(baseUser, accountViewModel) }, { ReportsTabHeader(baseUser, reportsFeedViewModel, accountViewModel) }, { RelaysTabHeader(baseUser, accountViewModel) }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/TabNotesNewThreads.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/TabNotesNewThreads.kt index c2d523570..85d03e3ac 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/TabNotesNewThreads.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/TabNotesNewThreads.kt @@ -20,28 +20,203 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.newthreads +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.PushPin +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState +import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty +import com.vitorpamplona.amethyst.ui.feeds.FeedError +import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed +import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox +import com.vitorpamplona.amethyst.ui.feeds.WatchScrollToTop import com.vitorpamplona.amethyst.ui.navigation.navs.INav -import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView +import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.newthreads.dal.UserProfileNewThreadsFeedViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.pinnedNotes.dal.UserProfilePinnedNotesFeedViewModel +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.DividerThickness +import com.vitorpamplona.amethyst.ui.theme.FeedPadding @Composable fun TabNotesNewThreads( feedViewModel: UserProfileNewThreadsFeedViewModel, + pinnedNotesFeedViewModel: UserProfilePinnedNotesFeedViewModel, accountViewModel: AccountViewModel, nav: INav, ) { + LaunchedEffect(Unit) { pinnedNotesFeedViewModel.invalidateData() } + Column(Modifier.fillMaxHeight()) { - RefresheableFeedView( - feedViewModel, - null, - enablePullRefresh = false, - accountViewModel = accountViewModel, - nav = nav, + RefresheableBox(feedViewModel, enablePullRefresh = false) { + val listState = + androidx.compose.foundation.lazy + .rememberLazyListState() + + WatchScrollToTop(feedViewModel.feedState, listState) + + val feedState by feedViewModel.feedState.feedContent.collectAsStateWithLifecycle() + val pinnedFeedState by pinnedNotesFeedViewModel.feedState.feedContent.collectAsStateWithLifecycle() + + when (val state = feedState) { + is FeedState.Empty -> { + val pinnedLoaded = pinnedFeedState as? FeedState.Loaded + if (pinnedLoaded != null) { + val pinnedItems by pinnedLoaded.feed.collectAsStateWithLifecycle() + if (pinnedItems.list.isNotEmpty()) { + FeedLoadedWithPinnedNotes( + pinnedFeedState = pinnedLoaded, + loaded = null, + listState = listState, + accountViewModel = accountViewModel, + nav = nav, + ) + } else { + FeedEmpty { feedViewModel.invalidateData() } + } + } else { + FeedEmpty { feedViewModel.invalidateData() } + } + } + + is FeedState.FeedError -> { + FeedError(state.errorMessage) { feedViewModel.invalidateData() } + } + + is FeedState.Loaded -> { + FeedLoadedWithPinnedNotes( + pinnedFeedState = pinnedFeedState as? FeedState.Loaded, + loaded = state, + listState = listState, + accountViewModel = accountViewModel, + nav = nav, + ) + } + + is FeedState.Loading -> { + LoadingFeed() + } + } + } + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun FeedLoadedWithPinnedNotes( + pinnedFeedState: FeedState.Loaded?, + loaded: FeedState.Loaded?, + listState: LazyListState, + accountViewModel: AccountViewModel, + nav: INav, +) { + val pinnedItems = + pinnedFeedState?.let { + val state by it.feed.collectAsStateWithLifecycle() + state + } + + val feedItems = + loaded?.let { + val state by it.feed.collectAsStateWithLifecycle() + state + } + + LazyColumn( + contentPadding = FeedPadding, + state = listState, + ) { + if (pinnedItems != null && pinnedItems.list.isNotEmpty()) { + items( + pinnedItems.list, + key = { "pinned-${it.idHex}" }, + contentType = { it.event?.kind ?: -1 }, + ) { item -> + Column(Modifier.fillMaxWidth().animateItem()) { + PinnedNoteLabel() + NoteCompose( + item, + modifier = Modifier.fillMaxWidth(), + routeForLastRead = null, + isBoostedNote = false, + isHiddenFeed = pinnedItems.showHidden, + quotesLeft = 3, + accountViewModel = accountViewModel, + nav = nav, + ) + } + HorizontalDivider(thickness = DividerThickness) + } + } + + if (feedItems != null) { + itemsIndexed( + feedItems.list, + key = { _, item -> item.idHex }, + contentType = { _, item -> item.event?.kind ?: -1 }, + ) { _, item -> + Row(Modifier.fillMaxWidth().animateItem()) { + NoteCompose( + item, + modifier = Modifier.fillMaxWidth(), + routeForLastRead = null, + isBoostedNote = false, + isHiddenFeed = feedItems.showHidden, + quotesLeft = 3, + accountViewModel = accountViewModel, + nav = nav, + ) + } + HorizontalDivider(thickness = DividerThickness) + } + } + } +} + +@Composable +private fun PinnedNoteLabel() { + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(start = 16.dp, top = 8.dp, end = 16.dp, bottom = 0.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = Icons.Default.PushPin, + contentDescription = stringRes(R.string.pinned_notes), + modifier = Modifier.size(14.dp), + tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), + ) + Spacer(Modifier.width(4.dp)) + Text( + text = stringRes(R.string.pinned_notes), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f), ) } }