From c9d1a3439c506342e7d78c39a4d82bca9ad65a21 Mon Sep 17 00:00:00 2001 From: OneWay Date: Mon, 12 Oct 2026 15:36:18 +0800 Subject: [PATCH] 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. --- .../research/ui/main/AdaptiveMainScreen.kt | 3 ++- .../example/research/ui/main/MainScreen.kt | 1 + .../research/ui/main/SearchResultsPane.kt | 22 ++++++++++++++++++- .../com/example/research/ui/theme/Spacing.kt | 1 + 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/research/ui/main/AdaptiveMainScreen.kt b/app/src/main/java/com/example/research/ui/main/AdaptiveMainScreen.kt index 8856e35..b9db0bc 100644 --- a/app/src/main/java/com/example/research/ui/main/AdaptiveMainScreen.kt +++ b/app/src/main/java/com/example/research/ui/main/AdaptiveMainScreen.kt @@ -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) diff --git a/app/src/main/java/com/example/research/ui/main/MainScreen.kt b/app/src/main/java/com/example/research/ui/main/MainScreen.kt index 3a26d7b..75441bc 100644 --- a/app/src/main/java/com/example/research/ui/main/MainScreen.kt +++ b/app/src/main/java/com/example/research/ui/main/MainScreen.kt @@ -216,6 +216,7 @@ internal fun MainScreen( .fillMaxWidth() .windowInsetsPadding(AppWindowInsets.horizontalSafeDrawing) .padding(start = Spacing.contentStart, end = 16.dp), + alignFirstResultWithTitleBar = true, ) searchBar() } diff --git a/app/src/main/java/com/example/research/ui/main/SearchResultsPane.kt b/app/src/main/java/com/example/research/ui/main/SearchResultsPane.kt index b6867aa..d4f17c1 100644 --- a/app/src/main/java/com/example/research/ui/main/SearchResultsPane.kt +++ b/app/src/main/java/com/example/research/ui/main/SearchResultsPane.kt @@ -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) + } +} diff --git a/app/src/main/java/com/example/research/ui/theme/Spacing.kt b/app/src/main/java/com/example/research/ui/theme/Spacing.kt index 0da25a0..92d8ad1 100644 --- a/app/src/main/java/com/example/research/ui/theme/Spacing.kt +++ b/app/src/main/java/com/example/research/ui/theme/Spacing.kt @@ -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