feat(search): line up the first result with the article title bar

With the search bar at the bottom, offset the results list so the
first result sits on the article title row and the back button
lands under the finger, in single- and two-pane layouts alike.
This commit is contained in:
OneWay committed 2026-10-12 15:36:18 +08:00
1 parent 41bc116c56
commit c9d1a3439c
4 files changed
+25 -2

No files matched your search

@@ -107,6 +107,7 @@ fun AdaptiveMainScreen(
)
.padding(start = Spacing.contentStart, end = listEndPadding),
listModifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars),
alignFirstResultWithTitleBar = isSearchBarAtBottom,
)
},
detailPane = {
@@ -124,7 +125,7 @@ fun AdaptiveMainScreen(
articleState = articleState,
showBackButton = isShowingDetail && articleState != null,
onNavigateBack = onNavigateBackFromArticle,
compact = true,
compact = !isSearchBarAtBottom,
selectionReset = titleSelectionReset.intValue,
modifier = Modifier.windowInsetsPadding(
WindowInsets.safeDrawing.only(WindowInsetsSides.Top)
@@ -216,6 +216,7 @@ internal fun MainScreen(
.fillMaxWidth()
.windowInsetsPadding(AppWindowInsets.horizontalSafeDrawing)
.padding(start = Spacing.contentStart, end = 16.dp),
alignFirstResultWithTitleBar = true,
)
searchBar()
}
@@ -16,7 +16,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.text.rememberTextMeasurer
import com.example.research.common.ui.theme.dictionaryTitleLarge
import com.example.research.ui.theme.Spacing
import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems
import com.example.research.core.domain.model.IndexEntry
@@ -43,6 +48,7 @@ internal fun SearchResultsPane(
onArticleClick: (IndexEntry) -> Unit,
modifier: Modifier = Modifier,
listModifier: Modifier = Modifier,
alignFirstResultWithTitleBar: Boolean = false,
) {
val listState = rememberLazyListState(
initialFirstVisibleItemIndex = scrollPosition.first,
@@ -55,6 +61,7 @@ internal fun SearchResultsPane(
val imeBottomPadding = with(LocalDensity.current) {
WindowInsets.ime.getBottom(this).toDp()
}
val topPadding = if (alignFirstResultWithTitleBar) titleBarAlignedTopPadding() else 0.dp
LaunchedEffect(listState) {
snapshotFlow {
@@ -139,8 +146,21 @@ internal fun SearchResultsPane(
modifier = listModifier.weight(1f),
listState = listState,
showDictionaryName = showDictionaryName,
contentPadding = PaddingValues(bottom = imeBottomPadding),
contentPadding = PaddingValues(top = topPadding, bottom = imeBottomPadding),
staleEntries = shownStale,
)
}
}
@Composable
private fun titleBarAlignedTopPadding(): Dp {
val textMeasurer = rememberTextMeasurer(cacheSize = 0)
val style = MaterialTheme.typography.dictionaryTitleLarge
val density = LocalDensity.current
return remember(textMeasurer, style, density) {
val titleHeight = with(density) {
textMeasurer.measure(text = "A", style = style, maxLines = 1).size.height.toDp()
}
(Spacing.topBarCenter - Spacing.listItemVertical - titleHeight / 2).coerceAtLeast(0.dp)
}
}
@@ -6,6 +6,7 @@ object Spacing {
// Semantic spacing constants for specific UI elements
val topBarPadding = 8.dp
val topBarHeight = 56.dp
val topBarCenter = topBarPadding + topBarHeight / 2
val listItemVertical = 8.dp
val contentStart = 32.dp