fix: honor Profile UI toggles in subscriptions and pager state
- Gate WatchLifecycleAndUpdateModel(appRecommendations) on the showProfileAppRecommendations toggle so the viewmodel no longer refreshes when the section is hidden. - Thread loadFollowers / loadZapsReceived through UserProfileQueryState and the matching sub-assemblers. When the user hides those tabs, UserProfileFilterAssembler stops emitting their relay filters and the live REQs drop. - Re-pin the profile pager to the tab the user was viewing whenever the visible-tabs list changes, using key(visibleTabs) + rememberPagerState(initialPage = ...) and a snapshotFlow that tracks the currently-viewed ProfileTab. Prevents the pager from silently shifting to a different tab when one is toggled off in settings.
This commit is contained in:
+81
-47
@@ -46,10 +46,12 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.key
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -288,15 +290,27 @@ fun ProfileScreen(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
|
val ui = accountViewModel.settings.uiSettingsFlow
|
||||||
|
val showAppRecommendations by ui.showProfileAppRecommendations.collectAsStateWithLifecycle()
|
||||||
|
val showFollowersFeed by ui.showProfileFollowersFeed.collectAsStateWithLifecycle()
|
||||||
|
val showZapReceivedFeed by ui.showProfileZapReceivedFeed.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
WatchLifecycleAndUpdateModel(threadsViewModel)
|
WatchLifecycleAndUpdateModel(threadsViewModel)
|
||||||
WatchLifecycleAndUpdateModel(repliesViewModel)
|
WatchLifecycleAndUpdateModel(repliesViewModel)
|
||||||
WatchLifecycleAndUpdateModel(mutualViewModel)
|
WatchLifecycleAndUpdateModel(mutualViewModel)
|
||||||
WatchLifecycleAndUpdateModel(appRecommendations)
|
if (showAppRecommendations) {
|
||||||
|
WatchLifecycleAndUpdateModel(appRecommendations)
|
||||||
|
}
|
||||||
WatchLifecycleAndUpdateModel(bookmarksFeedViewModel)
|
WatchLifecycleAndUpdateModel(bookmarksFeedViewModel)
|
||||||
WatchLifecycleAndUpdateModel(pinnedNotesFeedViewModel)
|
WatchLifecycleAndUpdateModel(pinnedNotesFeedViewModel)
|
||||||
WatchLifecycleAndUpdateModel(galleryFeedViewModel)
|
WatchLifecycleAndUpdateModel(galleryFeedViewModel)
|
||||||
|
|
||||||
UserProfileFilterAssemblerSubscription(baseUser, accountViewModel.dataSources().profile)
|
UserProfileFilterAssemblerSubscription(
|
||||||
|
user = baseUser,
|
||||||
|
loadFollowers = showFollowersFeed,
|
||||||
|
loadZapsReceived = showZapReceivedFeed,
|
||||||
|
assembler = accountViewModel.dataSources().profile,
|
||||||
|
)
|
||||||
|
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
@@ -461,54 +475,74 @@ private fun RenderScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val pagerState = rememberPagerState(pageCount = { visibleTabs.size })
|
// Track the tab the user was viewing so we can re-pin to it when the
|
||||||
|
// visible-tabs list changes (e.g. they hid Followers from settings while
|
||||||
|
// standing on the Zaps tab).
|
||||||
|
var viewedTab by remember { mutableStateOf(visibleTabs.first()) }
|
||||||
|
|
||||||
Column {
|
// key() rebuilds the pager state whenever the visible-tabs list changes,
|
||||||
ProfileHeader(baseUser, appRecommendations, externalIdentities, nav, accountViewModel)
|
// re-initializing it on the same tab the user was looking at.
|
||||||
SecondaryScrollableTabRow(
|
key(visibleTabs) {
|
||||||
containerColor = Color.Transparent,
|
val pagerState =
|
||||||
contentColor = MaterialTheme.colorScheme.onBackground,
|
rememberPagerState(
|
||||||
selectedTabIndex = pagerState.currentPage,
|
initialPage = visibleTabs.indexOf(viewedTab).coerceAtLeast(0),
|
||||||
edgePadding = Size8dp,
|
pageCount = { visibleTabs.size },
|
||||||
modifier = tabRowModifier,
|
|
||||||
divider = { HorizontalDivider(thickness = DividerThickness) },
|
|
||||||
) {
|
|
||||||
CreateAndRenderTabs(
|
|
||||||
baseUser,
|
|
||||||
pagerState,
|
|
||||||
visibleTabs,
|
|
||||||
threadsViewModel,
|
|
||||||
repliesViewModel,
|
|
||||||
mutualViewModel,
|
|
||||||
followsFeedViewModel,
|
|
||||||
followersFeedViewModel,
|
|
||||||
zapFeedViewModel,
|
|
||||||
bookmarksFeedViewModel,
|
|
||||||
galleryFeedViewModel,
|
|
||||||
reportsFeedViewModel,
|
|
||||||
accountViewModel,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(pagerState, visibleTabs) {
|
||||||
|
snapshotFlow { pagerState.currentPage }
|
||||||
|
.collect { page ->
|
||||||
|
visibleTabs.getOrNull(page)?.let { viewedTab = it }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
HorizontalPager(
|
|
||||||
state = pagerState,
|
Column {
|
||||||
modifier = pagerModifier,
|
ProfileHeader(baseUser, appRecommendations, externalIdentities, nav, accountViewModel)
|
||||||
) { page ->
|
SecondaryScrollableTabRow(
|
||||||
CreateAndRenderPages(
|
containerColor = Color.Transparent,
|
||||||
visibleTabs[page],
|
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||||
baseUser,
|
selectedTabIndex = pagerState.currentPage,
|
||||||
threadsViewModel,
|
edgePadding = Size8dp,
|
||||||
repliesViewModel,
|
modifier = tabRowModifier,
|
||||||
mutualViewModel,
|
divider = { HorizontalDivider(thickness = DividerThickness) },
|
||||||
followsFeedViewModel,
|
) {
|
||||||
followersFeedViewModel,
|
CreateAndRenderTabs(
|
||||||
zapFeedViewModel,
|
baseUser,
|
||||||
bookmarksFeedViewModel,
|
pagerState,
|
||||||
pinnedNotesFeedViewModel,
|
visibleTabs,
|
||||||
galleryFeedViewModel,
|
threadsViewModel,
|
||||||
reportsFeedViewModel,
|
repliesViewModel,
|
||||||
accountViewModel,
|
mutualViewModel,
|
||||||
nav,
|
followsFeedViewModel,
|
||||||
)
|
followersFeedViewModel,
|
||||||
|
zapFeedViewModel,
|
||||||
|
bookmarksFeedViewModel,
|
||||||
|
galleryFeedViewModel,
|
||||||
|
reportsFeedViewModel,
|
||||||
|
accountViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
HorizontalPager(
|
||||||
|
state = pagerState,
|
||||||
|
modifier = pagerModifier,
|
||||||
|
) { page ->
|
||||||
|
CreateAndRenderPages(
|
||||||
|
visibleTabs[page],
|
||||||
|
baseUser,
|
||||||
|
threadsViewModel,
|
||||||
|
repliesViewModel,
|
||||||
|
mutualViewModel,
|
||||||
|
followsFeedViewModel,
|
||||||
|
followersFeedViewModel,
|
||||||
|
zapFeedViewModel,
|
||||||
|
bookmarksFeedViewModel,
|
||||||
|
pinnedNotesFeedViewModel,
|
||||||
|
galleryFeedViewModel,
|
||||||
|
reportsFeedViewModel,
|
||||||
|
accountViewModel,
|
||||||
|
nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -27,6 +27,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
|||||||
// This allows multiple screen to be listening to tags, even the same tag
|
// This allows multiple screen to be listening to tags, even the same tag
|
||||||
class UserProfileQueryState(
|
class UserProfileQueryState(
|
||||||
val user: User,
|
val user: User,
|
||||||
|
val loadFollowers: Boolean,
|
||||||
|
val loadZapsReceived: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
class UserProfileFilterAssembler(
|
class UserProfileFilterAssembler(
|
||||||
|
|||||||
+4
-2
@@ -28,13 +28,15 @@ import com.vitorpamplona.amethyst.model.User
|
|||||||
@Composable
|
@Composable
|
||||||
fun UserProfileFilterAssemblerSubscription(
|
fun UserProfileFilterAssemblerSubscription(
|
||||||
user: User,
|
user: User,
|
||||||
|
loadFollowers: Boolean,
|
||||||
|
loadZapsReceived: Boolean,
|
||||||
assembler: UserProfileFilterAssembler,
|
assembler: UserProfileFilterAssembler,
|
||||||
) {
|
) {
|
||||||
// different screens get different states
|
// different screens get different states
|
||||||
// even if they are tracking the same tag.
|
// even if they are tracking the same tag.
|
||||||
val state =
|
val state =
|
||||||
remember(user) {
|
remember(user, loadFollowers, loadZapsReceived) {
|
||||||
UserProfileQueryState(user)
|
UserProfileQueryState(user, loadFollowers, loadZapsReceived)
|
||||||
}
|
}
|
||||||
|
|
||||||
LifecycleAwareKeyDataSourceSubscription(state, assembler)
|
LifecycleAwareKeyDataSourceSubscription(state, assembler)
|
||||||
|
|||||||
+6
-1
@@ -32,7 +32,12 @@ class UserProfileFollowersFilterSubAssembler(
|
|||||||
override fun updateFilter(
|
override fun updateFilter(
|
||||||
key: UserProfileQueryState,
|
key: UserProfileQueryState,
|
||||||
since: SincePerRelayMap?,
|
since: SincePerRelayMap?,
|
||||||
): List<RelayBasedFilter> = filterUserProfileFollowers(user(key), since)
|
): List<RelayBasedFilter> =
|
||||||
|
if (key.loadFollowers) {
|
||||||
|
filterUserProfileFollowers(user(key), since)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
override fun user(key: UserProfileQueryState) = key.user
|
override fun user(key: UserProfileQueryState) = key.user
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-3
@@ -33,9 +33,13 @@ class UserProfileZapsFilterSubAssembler(
|
|||||||
key: UserProfileQueryState,
|
key: UserProfileQueryState,
|
||||||
since: SincePerRelayMap?,
|
since: SincePerRelayMap?,
|
||||||
): List<RelayBasedFilter> =
|
): List<RelayBasedFilter> =
|
||||||
listOfNotNull(
|
if (key.loadZapsReceived) {
|
||||||
filterUserProfileZapsReceived(user(key), since),
|
listOfNotNull(
|
||||||
).flatten()
|
filterUserProfileZapsReceived(user(key), since),
|
||||||
|
).flatten()
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
override fun user(key: UserProfileQueryState) = key.user
|
override fun user(key: UserProfileQueryState) = key.user
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user