fix(home-banner): float DVM status banner over the feed instead of in the top bar

Putting HomeDvmStatusBanner inside the top-bar Column meant the
SecondaryTabRow shifted up/down every time the banner appeared or
disappeared (filter selection, refresh, response arrival). Annoying.

Move the banner into a Box that wraps the HorizontalPager and align
it to TopCenter so it floats over the feed:
- topBar Column is back to just HomeTopBar + SecondaryTabRow; tabs
  no longer reflow when the banner toggles.
- BannerCard takes a Modifier and gets tonalElevation + shadowElevation
  + a slightly stronger surface tint so it reads as a floating overlay
  rather than part of the scaffold chrome.
This commit is contained in:
Claude
2026-04-19 15:53:23 +00:00
parent dc41432c5b
commit b0f75d4dd4
2 changed files with 54 additions and 25 deletions
@@ -58,13 +58,14 @@ import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
fun HomeDvmStatusBanner(
accountViewModel: AccountViewModel,
nav: INav,
modifier: Modifier = Modifier,
) {
val topFilter by accountViewModel.account.settings.defaultHomeFollowList
.collectAsStateWithLifecycle()
when (val filter = topFilter) {
is TopFilter.FavoriteDvm -> SingleDvmBanner(filter, accountViewModel, nav)
is TopFilter.AllFavoriteDvms -> AllFavoriteDvmsBanner(accountViewModel)
is TopFilter.FavoriteDvm -> SingleDvmBanner(filter, accountViewModel, nav, modifier)
is TopFilter.AllFavoriteDvms -> AllFavoriteDvmsBanner(accountViewModel, modifier)
else -> Unit
}
}
@@ -74,6 +75,7 @@ private fun SingleDvmBanner(
favDvm: TopFilter.FavoriteDvm,
accountViewModel: AccountViewModel,
nav: INav,
modifier: Modifier = Modifier,
) {
val snapshot by accountViewModel.account.favoriteDvmOrchestrator
.observe(favDvm.address)
@@ -95,7 +97,7 @@ private fun SingleDvmBanner(
?: ""
}
BannerCard {
BannerCard(modifier) {
val status = snapshot.latestStatus?.status()
when {
@@ -167,7 +169,10 @@ private fun SingleDvmBanner(
}
@Composable
private fun AllFavoriteDvmsBanner(accountViewModel: AccountViewModel) {
private fun AllFavoriteDvmsBanner(
accountViewModel: AccountViewModel,
modifier: Modifier = Modifier,
) {
val addresses by accountViewModel.account.favoriteDvmList.flow
.collectAsStateWithLifecycle()
@@ -189,7 +194,7 @@ private fun AllFavoriteDvmsBanner(accountViewModel: AccountViewModel) {
val allErrored = snapshots.all { it.errorMessage != null || it.latestStatus?.status()?.code == "error" }
BannerCard {
BannerCard(modifier) {
if (allErrored) {
BannerMessageRow(
message = stringRes(R.string.dvm_home_status_error),
@@ -209,14 +214,22 @@ private fun AllFavoriteDvmsBanner(accountViewModel: AccountViewModel) {
}
@Composable
private fun BannerCard(content: @Composable () -> Unit) {
private fun BannerCard(
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
// Sits above the feed instead of in the topBar Column, so adding/removing
// the banner doesn't shift the tabs/filter up and down. tonalElevation
// gives it a faint surface tint so it reads as a floating overlay.
Surface(
modifier =
Modifier
modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 6.dp),
shape = RoundedCornerShape(12.dp),
color = MaterialTheme.colorScheme.surfaceContainerLow,
color = MaterialTheme.colorScheme.surfaceContainerHigh,
tonalElevation = 4.dp,
shadowElevation = 4.dp,
) {
Column(modifier = Modifier.padding(12.dp)) { content() }
}
@@ -23,11 +23,14 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.home
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
@@ -52,6 +55,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.R
@@ -179,7 +183,6 @@ private fun HomePages(
topBar = {
Column {
HomeTopBar(accountViewModel, nav)
HomeDvmStatusBanner(accountViewModel, nav)
SecondaryTabRow(
containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.onBackground,
@@ -210,24 +213,37 @@ private fun HomePages(
HomeScreenFloatingButton(accountViewModel, nav)
},
accountViewModel = accountViewModel,
) {
HorizontalPager(
contentPadding = it,
state = pagerState,
userScrollEnabled = true,
modifier =
Modifier.zonedDrawerSwipe(
pagerState = pagerState,
openDrawer = nav::openDrawer,
),
) { page ->
HomeFeeds(
feedState = tabs[page].feedState,
routeForLastRead = tabs[page].routeForLastRead,
scrollStateKey = tabs[page].scrollStateKey,
liveSection = tabs[page].liveSection,
) { paddingValues ->
// Wrap pager + banner in a Box so the banner can float over the feed
// (anchored top-center) instead of living in the topBar Column where
// it would push the tabs down every time it appears or disappears.
Box(
modifier = Modifier.fillMaxSize().padding(paddingValues),
) {
HorizontalPager(
contentPadding = PaddingValues(0.dp),
state = pagerState,
userScrollEnabled = true,
modifier =
Modifier.zonedDrawerSwipe(
pagerState = pagerState,
openDrawer = nav::openDrawer,
),
) { page ->
HomeFeeds(
feedState = tabs[page].feedState,
routeForLastRead = tabs[page].routeForLastRead,
scrollStateKey = tabs[page].scrollStateKey,
liveSection = tabs[page].liveSection,
accountViewModel = accountViewModel,
nav = nav,
)
}
HomeDvmStatusBanner(
accountViewModel = accountViewModel,
nav = nav,
modifier = Modifier.align(Alignment.TopCenter),
)
}
}