android, desktop: show the Names and Directory headers above their results with the one-hand UI

The one-hand chat list is reversed, so a header emitted before its rows was drawn below them. searchPart puts the header after the rows in that layout, as the iOS fix does; the rows keep their order, nearest the search field. The onboarding results list, whose first item is now a keyed row, scrolls to its start when name results arrive, since the list would otherwise hold a directory row in place and leave the names below it. Spec links into ChatListView.kt refreshed.
This commit is contained in:
spacedandy-agents-simplex authored and spaced4ndy committed 2026-10-16 13:50:13 +04:00
1 parent 01eb96c841
commit 831396edf6
2 files changed
+64 -43

No files matched your search

@@ -471,12 +471,20 @@ private fun BoxScope.ChatListWithLoadingScreen(searchText: MutableState<TextFiel
Box(Modifier.weight(1f).fillMaxWidth()) {
if (onlineSearch.showResults) {
val rows = onlineSearch.rows(shownChatIds = emptySet())
val resultsState = rememberLazyListState()
val hasNames = rows.names.isNotEmpty()
// the list keeps its first visible row in place by key, so in one-hand UI names arriving after
// the directory page would be inserted below that row, out of view
LaunchedEffect(hasNames) {
if (hasNames && oneHandUI.value) resultsState.scrollToItem(0)
}
LazyColumnWithScrollBarNoAppBar(
if (!oneHandUI.value) Modifier.fillMaxSize().imePadding() else Modifier.fillMaxSize(),
state = resultsState,
reverseLayout = oneHandUI.value
) {
nameRows(rows.names)
directoryRows(rows, onlineSearch)
nameRows(rows.names, oneHandUI.value)
directoryRows(rows, onlineSearch, oneHandUI.value)
if (!onlineSearch.searching && rows.isEmpty) {
item {
Text(
@@ -506,43 +514,56 @@ private fun BoxScope.ChatListWithLoadingScreen(searchText: MutableState<TextFiel
}
}
private fun LazyListScope.nameRows(names: List<NameSearchResult>) {
private fun LazyListScope.nameRows(names: List<NameSearchResult>, oneHandUI: Boolean) {
if (names.isNotEmpty()) {
item { SearchPartHeader(stringResource(MR.strings.online_search_names)) }
items(names, key = { it.id }) { result ->
val view = LocalMultiplatformView()
NameSearchRow(
result,
Modifier.clickable {
hideKeyboard(view)
withBGApi { planAndConnect(chatModel.remoteHostId(), result.target, close = null, cleanup = null) }
}
)
searchPart(MR.strings.online_search_names, oneHandUI) {
items(names, key = { it.id }) { result ->
val view = LocalMultiplatformView()
NameSearchRow(
result,
Modifier.clickable {
hideKeyboard(view)
withBGApi { planAndConnect(chatModel.remoteHostId(), result.target, close = null, cleanup = null) }
}
)
}
}
}
}
private fun LazyListScope.directoryRows(rows: OnlineSearchRows, onlineSearch: OnlineSearchModel) {
private fun LazyListScope.directoryRows(rows: OnlineSearchRows, onlineSearch: OnlineSearchModel, oneHandUI: Boolean) {
if (rows.directory.isNotEmpty() || rows.footer != null) {
item { SearchPartHeader(stringResource(MR.strings.online_search_directory)) }
items(rows.directory, key = { it.id }) { result ->
val view = LocalMultiplatformView()
DirectorySearchRow(
result.entry,
Modifier.clickable {
hideKeyboard(view)
withBGApi { planAndConnect(chatModel.remoteHostId(), result.entry.link, close = null, cleanup = null) }
}
)
}
when (rows.footer) {
DirectoryFooter.Retry -> item { DirectoryRetryRow(onlineSearch) }
DirectoryFooter.ShowMore -> item { DirectoryShowMoreRow(onlineSearch) }
null -> {}
searchPart(MR.strings.online_search_directory, oneHandUI) {
items(rows.directory, key = { it.id }) { result ->
val view = LocalMultiplatformView()
DirectorySearchRow(
result.entry,
Modifier.clickable {
hideKeyboard(view)
withBGApi { planAndConnect(chatModel.remoteHostId(), result.entry.link, close = null, cleanup = null) }
}
)
}
when (rows.footer) {
DirectoryFooter.Retry -> item { DirectoryRetryRow(onlineSearch) }
DirectoryFooter.ShowMore -> item { DirectoryShowMoreRow(onlineSearch) }
null -> {}
}
}
}
}
// the list is reversed in one-hand UI, so there the header follows its rows to be drawn above them
private fun LazyListScope.searchPart(title: StringResource, oneHandUI: Boolean, content: LazyListScope.() -> Unit) {
if (oneHandUI) {
content()
item { SearchPartHeader(stringResource(title)) }
} else {
item { SearchPartHeader(stringResource(title)) }
content()
}
}
@Composable
private fun SearchPartHeader(title: String) {
Text(
@@ -1181,8 +1202,8 @@ private fun BoxScope.ChatList(searchText: MutableState<TextFieldValue>, listStat
} }
ChatListNavLinkView(chat, nextChatSelected)
}
nameRows(rows.names)
directoryRows(rows, onlineSearch)
nameRows(rows.names, oneHandUI.value)
directoryRows(rows, onlineSearch, oneHandUI.value)
if (!addressCreationCardShown.value) {
item {
ChatListFeatureCards()