use lazy grid for search results

This commit is contained in:
MM20
2026-03-21 12:54:30 +01:00
parent 228ae63bc9
commit 32031d6ef8
15 changed files with 129 additions and 71 deletions

View File

@@ -4,7 +4,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
@@ -42,24 +42,24 @@ internal class SearchComponent(
state: LauncherScaffoldState
) {
val searchVM = viewModel<SearchVM>()
val lazyListState = rememberLazyListState()
val lazyGridState = rememberLazyGridState()
LaunchedEffect(isActive) {
if (!isActive) {
searchVM.reset()
lazyListState.scrollToItem(0, 0)
lazyGridState.scrollToItem(0, 0)
}
}
LaunchedEffect(searchVM.searchQuery.value, searchVM.filters.value) {
lazyListState.requestScrollToItem(0, 0)
lazyGridState.requestScrollToItem(0, 0)
}
LaunchedEffect(lazyListState.canScrollForward, lazyListState.canScrollBackward) {
LaunchedEffect(lazyGridState.canScrollForward, lazyGridState.canScrollBackward) {
isAtBottom.value =
!lazyListState.canScrollForward && !reverse || !lazyListState.canScrollBackward && reverse
!lazyGridState.canScrollForward && !reverse || !lazyGridState.canScrollBackward && reverse
isAtTop.value =
!lazyListState.canScrollForward && reverse || !lazyListState.canScrollBackward && !reverse
!lazyGridState.canScrollForward && reverse || !lazyGridState.canScrollBackward && !reverse
}
@@ -88,7 +88,7 @@ internal class SearchComponent(
SearchColumn(
modifier = Modifier.nestedScroll(scrollConnection).widthIn(max = 916.dp).fillMaxHeight(),
paddingValues = insets,
state = lazyListState,
state = lazyGridState,
reverse = reverse,
userScrollEnabled = !state.isDragged,
onHideKeyboard = {

View File

@@ -12,6 +12,11 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.surfaceColorAtElevation
@@ -58,7 +63,7 @@ import de.mm20.launcher2.ui.theme.transparency.transparency
fun SearchColumn(
modifier: Modifier = Modifier,
paddingValues: PaddingValues = PaddingValues(0.dp),
state: LazyListState = rememberLazyListState(),
state: LazyGridState = rememberLazyGridState(),
reverse: Boolean = false,
userScrollEnabled: Boolean = true,
onHideKeyboard: () -> Unit = {},
@@ -156,11 +161,12 @@ fun SearchColumn(
)
}
} else {
LazyColumn(
LazyVerticalGrid(
state = state,
userScrollEnabled = userScrollEnabled,
contentPadding = paddingValues,
reverseLayout = reverse,
columns = GridCells.Fixed(columns),
) {
if (!hideFavs && favoritesEnabled) {
SearchFavorites(
@@ -178,7 +184,7 @@ fun SearchColumn(
)
} else {
// Empty item to maintain scroll position
item(key = "favorites") {
item(key = "favorites", span = { GridItemSpan(maxLineSpan) }) {
}
}

View File

@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.FilledTonalButton
@@ -35,7 +36,7 @@ import de.mm20.launcher2.ui.launcher.search.common.list.ListResults
import de.mm20.launcher2.ui.layout.BottomReversed
import de.mm20.launcher2.ui.locals.LocalGridSettings
fun LazyListScope.AppResults(
fun LazyGridScope.AppResults(
onProfileSelected: (Int) -> Unit,
profiles: List<Profile> = emptyList(),
selectedProfileIndex: Int = -1,

View File

@@ -1,15 +1,21 @@
package de.mm20.launcher2.ui.launcher.search.calculator
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridScope
import de.mm20.launcher2.search.data.Calculator
import de.mm20.launcher2.ui.launcher.search.common.list.ListItemSurface
fun LazyListScope.CalculatorResults(
fun LazyGridScope.CalculatorResults(
calculator: List<Calculator>,
reverse: Boolean,
) {
if (calculator.isNotEmpty()) {
item(key = "calculator") {
item(
key = "calculator",
span = {
GridItemSpan(maxLineSpan)
}
) {
ListItemSurface(
isFirst = true,
isLast = true,

View File

@@ -2,7 +2,7 @@ package de.mm20.launcher2.ui.launcher.search.calendar
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
@@ -16,7 +16,7 @@ import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
import de.mm20.launcher2.ui.launcher.search.common.list.ListResults
import kotlin.math.min
fun LazyListScope.CalendarResults(
fun LazyGridScope.CalendarResults(
events: List<CalendarEvent>,
missingPermission: Boolean,
onPermissionRequest: () -> Unit,
@@ -39,7 +39,7 @@ fun LazyListScope.CalendarResults(
.fillMaxWidth(),
item = calendar,
showDetails = showDetails,
onShowDetails = { onSelect(if(it) index else -1) },
onShowDetails = { onSelect(if (it) index else -1) },
highlight = highlightedItem?.key == calendar.key
)
},

View File

@@ -2,21 +2,29 @@ package de.mm20.launcher2.ui.launcher.search.common.grid
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.IntrinsicSize
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.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.ui.ktx.withCorners
import de.mm20.launcher2.ui.theme.transparency.transparency
import kotlin.math.ceil
fun <T : SavableSearchable> LazyListScope.GridResults(
fun <T : SavableSearchable> LazyGridScope.GridResults(
key: String,
items: List<T>,
itemContent: @Composable (T) -> Unit,
@@ -29,6 +37,9 @@ fun <T : SavableSearchable> LazyListScope.GridResults(
item(
key = "$key-before",
contentType = { "$key-before" },
span = {
GridItemSpan(maxLineSpan)
}
) {
val isTop = !reverse || items.isEmpty() && after == null
val isBottom = reverse || items.isEmpty() && after == null
@@ -54,54 +65,56 @@ fun <T : SavableSearchable> LazyListScope.GridResults(
}
val rows = ceil(items.size / columns.toFloat()).toInt()
// cells at the end of the grid that are empty
val emptySlots = rows * columns - items.size
items(
rows,
key = {
"$key-$it"
},
contentType = { key }
items.size,
key = { "$key-${items[it].key}" },
contentType = { key },
span = {
if (it == items.lastIndex) {
GridItemSpan(maxCurrentLineSpan)
} else {
GridItemSpan(1)
}
}
) {
val item = items[it]
val isFirst = it == 0 && before == null
val isLast = it == rows - 1 && after == null
Row(
val isFirstRow = before == null && it < columns
val isLastRow = after == null && it >= items.lastIndex - (items.lastIndex % columns)
val isFirstInRow = it % columns == 0
val isLastInRow = it % columns == columns - 1 || it == items.lastIndex
Box(
modifier = Modifier
.fillMaxWidth()
.padding(
top = if (reverse && isLast) 8.dp else 0.dp,
bottom = if (!reverse && isLast) 8.dp else 0.dp,
top = if (reverse && isLastRow) 8.dp else 0.dp,
bottom = if (!reverse && isLastRow) 8.dp else 0.dp,
)
.background(
MaterialTheme.colorScheme.surface.copy(alpha = MaterialTheme.transparency.surface),
MaterialTheme.shapes.medium.withCorners(
topStart = isFirst && !reverse || isLast && reverse,
topEnd = isFirst && !reverse || isLast && reverse,
bottomEnd = isLast && !reverse || isFirst && reverse,
bottomStart = isLast && !reverse || isFirst && reverse,
topStart = isFirstInRow && (isFirstRow && !reverse || isLastRow && reverse),
topEnd = isLastInRow && (isFirstRow && !reverse || isLastRow && reverse),
bottomStart = isFirstInRow && (isLastRow && !reverse || isFirstRow && reverse),
bottomEnd = isLastInRow && (isLastRow && !reverse || isFirstRow && reverse),
)
)
.padding(
top = if (it == 0) 8.dp else 0.dp,
bottom = if (it == rows - 1) 8.dp else 0.dp,
start = if (columns == 1) 0.dp else 4.dp,
end = if (columns == 1) 0.dp else 4.dp,
top = if (isFirstRow) 8.dp else 0.dp,
bottom = if (isLastRow) 8.dp else 0.dp,
start = if (columns == 1 || !isFirstInRow) 0.dp else 4.dp,
end = if (columns == 1 || !isLastInRow) 0.dp else 4.dp,
)
) {
Row {
for (i in 0 until columns) {
val item = items.getOrNull(it * columns + i)
if (item != null) {
Box(
modifier = Modifier
.weight(1f)
) {
itemContent(item)
}
} else {
Spacer(modifier = Modifier.weight(1f))
}
}
Box(
modifier = Modifier.fillMaxWidth(
if (it == items.lastIndex) 1f / (1f + emptySlots)
else 1f
)
) {
itemContent(item)
}
}
}
@@ -110,6 +123,9 @@ fun <T : SavableSearchable> LazyListScope.GridResults(
item(
key = "$key-after",
contentType = { "$key-after" },
span = {
GridItemSpan(maxLineSpan)
}
) {
val isTop = reverse || items.isEmpty() && before == null
val isBottom = !reverse || items.isEmpty() && before == null

View File

@@ -11,6 +11,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridItemScope
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@@ -24,7 +27,7 @@ import de.mm20.launcher2.ui.ktx.animateShapeAsState
import de.mm20.launcher2.ui.layout.BottomReversed
import de.mm20.launcher2.ui.theme.transparency.transparency
fun <T : SavableSearchable> LazyListScope.ListResults(
fun <T : SavableSearchable> LazyGridScope.ListResults(
key: String,
items: List<T>,
itemContent: @Composable ColumnScope.(T, Boolean, Int) -> Unit,
@@ -37,6 +40,9 @@ fun <T : SavableSearchable> LazyListScope.ListResults(
item(
key = "$key-before",
contentType = { "$key-before" },
span = {
GridItemSpan(maxLineSpan)
}
) {
ListItemSurface(
isFirst = true,
@@ -55,6 +61,9 @@ fun <T : SavableSearchable> LazyListScope.ListResults(
"$key-${items[it].key}"
},
contentType = { key },
span = {
GridItemSpan(maxLineSpan)
}
) {
val item = items[it]
val showDetails = it == selectedIndex
@@ -74,6 +83,9 @@ fun <T : SavableSearchable> LazyListScope.ListResults(
item(
key = "$key-after",
contentType = { "$key-after" },
span = {
GridItemSpan(maxLineSpan)
}
) {
ListItemSurface(
isFirst = before == null && items.isEmpty(),
@@ -88,7 +100,7 @@ fun <T : SavableSearchable> LazyListScope.ListResults(
}
@Composable
fun LazyItemScope.ListItemSurface(
fun LazyGridItemScope.ListItemSurface(
isFirst: Boolean = false,
isLast: Boolean = false,
reverse: Boolean = false,

View File

@@ -2,7 +2,7 @@ package de.mm20.launcher2.ui.launcher.search.contacts
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
@@ -16,7 +16,7 @@ import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
import de.mm20.launcher2.ui.launcher.search.common.list.ListResults
import kotlin.math.min
fun LazyListScope.ContactResults(
fun LazyGridScope.ContactResults(
contacts: List<Contact>,
missingPermission: Boolean,
onPermissionRequest: () -> Unit,

View File

@@ -5,6 +5,8 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
@@ -21,7 +23,7 @@ import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
import de.mm20.launcher2.ui.layout.BottomReversed
import de.mm20.launcher2.ui.theme.transparency.transparency
fun LazyListScope.SearchFavorites(
fun LazyGridScope.SearchFavorites(
favorites: List<SavableSearchable>,
pinnedTags: List<Tag>,
selectedTag: String?,
@@ -34,6 +36,9 @@ fun LazyListScope.SearchFavorites(
) {
item(
key = "favorites",
span = {
GridItemSpan(maxLineSpan)
}
) {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
Column(

View File

@@ -2,7 +2,7 @@ package de.mm20.launcher2.ui.launcher.search.files
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
@@ -16,7 +16,7 @@ import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
import de.mm20.launcher2.ui.launcher.search.common.list.ListResults
import kotlin.math.min
fun LazyListScope.FileResults(
fun LazyGridScope.FileResults(
files: List<File>,
missingPermission: Boolean,
onPermissionRequest: () -> Unit,

View File

@@ -2,7 +2,7 @@ package de.mm20.launcher2.ui.launcher.search.location
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
@@ -16,7 +16,7 @@ import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
import de.mm20.launcher2.ui.launcher.search.common.list.ListResults
import kotlin.math.min
fun LazyListScope.LocationResults(
fun LazyGridScope.LocationResults(
locations: List<Location>,
missingPermission: Boolean,
onPermissionRequest: () -> Unit,
@@ -39,7 +39,7 @@ fun LazyListScope.LocationResults(
.fillMaxWidth(),
item = location,
showDetails = showDetails,
onShowDetails = { onSelect(if(it) index else -1) },
onShowDetails = { onSelect(if (it) index else -1) },
highlight = highlightedItem?.key == location.key
)
},

View File

@@ -2,7 +2,7 @@ package de.mm20.launcher2.ui.launcher.search.shortcut
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
@@ -16,7 +16,7 @@ import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
import de.mm20.launcher2.ui.launcher.search.common.list.ListResults
import kotlin.math.min
fun LazyListScope.ShortcutResults(
fun LazyGridScope.ShortcutResults(
shortcuts: List<AppShortcut>,
missingPermission: Boolean,
onPermissionRequest: () -> Unit,

View File

@@ -11,6 +11,8 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -39,7 +41,7 @@ import de.mm20.launcher2.unitconverter.Dimension
import java.util.Date
import kotlin.math.min
fun LazyListScope.UnitConverterResults(
fun LazyGridScope.UnitConverterResults(
converters: List<UnitConverter>,
truncate: Boolean,
onShowAll: () -> Unit,
@@ -49,6 +51,9 @@ fun LazyListScope.UnitConverterResults(
val converter = converters.first()
item(
key = "converter-header",
span = {
GridItemSpan(maxLineSpan)
}
) {
ListItemSurface(
isFirst = true,
@@ -129,7 +134,10 @@ fun LazyListScope.UnitConverterResults(
val count = if (truncate) min(5, converter.values.size) else converter.values.size
items(
count,
key = { "converter-${converter.values[it].symbol}" }
key = { "converter-${converter.values[it].symbol}" },
span = {
GridItemSpan(maxLineSpan)
}
) {
val value = converter.values[it]
ListItemSurface(
@@ -193,7 +201,10 @@ fun LazyListScope.UnitConverterResults(
}
if (truncate && converter.values.size > 5) {
item(
key = "converter-footer"
key = "converter-footer",
span = {
GridItemSpan(maxLineSpan)
}
) {
ListItemSurface(
isLast = true,

View File

@@ -1,11 +1,12 @@
package de.mm20.launcher2.ui.launcher.search.website
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.LazyGridScope
import de.mm20.launcher2.search.Website
import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
import de.mm20.launcher2.ui.launcher.search.common.list.ListResults
fun LazyListScope.WebsiteResults(
fun LazyGridScope.WebsiteResults(
websites: List<Website>,
selectedIndex: Int,
onSelect: (Int) -> Unit,

View File

@@ -1,11 +1,11 @@
package de.mm20.launcher2.ui.launcher.search.wikipedia
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.LazyGridScope
import de.mm20.launcher2.search.Article
import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
import de.mm20.launcher2.ui.launcher.search.common.list.ListResults
fun LazyListScope.ArticleResults(
fun LazyGridScope.ArticleResults(
articles: List<Article>,
selectedIndex: Int,
onSelect: (Int) -> Unit,
@@ -19,7 +19,7 @@ fun LazyListScope.ArticleResults(
ListItem(
item = article,
showDetails = showDetails,
onShowDetails = { onSelect(if(it) index else -1) },
onShowDetails = { onSelect(if (it) index else -1) },
highlight = article.key == highlightedItem?.key,
)
},