fix(feeds): offset PullToRefresh indicator below top bar

Inside a DisappearingScaffold the content fills the full screen height so
feeds can scroll behind the top bar. PullToRefreshBox anchors its indicator
at the top of the box, which placed it behind the top bar.

Read LocalDisappearingScaffoldPadding inside RefresheableBox and pass a
custom indicator with that top padding applied, so the spinner surfaces
just below the bar. Screens not inside a DisappearingScaffold see the
default 0dp padding and behave as before.

https://claude.ai/code/session_01MgnMaw8U83rXPSnoGF4KBY
This commit is contained in:
Claude
2026-05-15 21:29:02 +00:00
parent f9bd4cd61c
commit 1d3c1edfea
@@ -23,15 +23,20 @@ package com.vitorpamplona.amethyst.ui.feeds
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.vitorpamplona.amethyst.ui.layouts.LocalDisappearingScaffoldPadding
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -54,10 +59,9 @@ fun RefresheableBox(
}
if (enablePullRefresh) {
PullToRefreshBox(
PullToRefreshBoxWithScaffoldPadding(
isRefreshing = isRefreshing,
onRefresh = onRefresh,
modifier = Modifier.fillMaxSize(),
content = content,
)
} else {
@@ -82,10 +86,45 @@ fun RefresheableBox(
}
}
PullToRefreshBox(
PullToRefreshBoxWithScaffoldPadding(
isRefreshing = isRefreshing,
onRefresh = onRefreshWrapped,
modifier = Modifier.fillMaxSize(),
content = content,
)
}
/**
* [PullToRefreshBox] anchors its indicator at the top of the box. Inside a
* [DisappearingScaffold] the content fills the full screen height (feed items
* scroll behind the top bar), so the default indicator would appear behind the
* top bar. Offset the indicator down by the scaffold's top padding so it
* surfaces just below the bar.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun PullToRefreshBoxWithScaffoldPadding(
isRefreshing: Boolean,
onRefresh: () -> Unit,
content: @Composable BoxScope.() -> Unit,
) {
val state = rememberPullToRefreshState()
val topPadding = LocalDisappearingScaffoldPadding.current.calculateTopPadding()
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = onRefresh,
state = state,
modifier = Modifier.fillMaxSize(),
indicator = {
PullToRefreshDefaults.Indicator(
modifier =
Modifier
.align(Alignment.TopCenter)
.padding(top = topPadding),
isRefreshing = isRefreshing,
state = state,
)
},
content = content,
)
}