Use lazy list for search results
This commit is contained in:
parent
74d02f9b4a
commit
f3d8d95d78
@ -5,6 +5,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.ui.locals.LocalCardStyle
|
||||
@ -14,11 +15,12 @@ fun LauncherCard(
|
||||
modifier: Modifier = Modifier,
|
||||
elevation: Dp = 2.dp,
|
||||
backgroundOpacity: Float = LocalCardStyle.current.opacity,
|
||||
shape: Shape = MaterialTheme.shapes.medium,
|
||||
content: @Composable () -> Unit = {}
|
||||
) {
|
||||
Surface(
|
||||
modifier = modifier,
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
shape = shape,
|
||||
border = LocalCardStyle.current.borderWidth.takeIf { it > 0 }
|
||||
?.let { BorderStroke(it.dp, MaterialTheme.colorScheme.surface) },
|
||||
content = content,
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
package de.mm20.launcher2.ui.launcher
|
||||
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.slideIn
|
||||
import androidx.compose.animation.slideOut
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.LocalOverscrollConfiguration
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.FractionalThreshold
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Done
|
||||
@ -61,13 +61,26 @@ fun PagerScaffold(
|
||||
val isWidgetEditMode by viewModel.isWidgetEditMode.observeAsState(false)
|
||||
|
||||
val widgetsScrollState = rememberScrollState()
|
||||
val searchScrollState = rememberScrollState()
|
||||
val searchState = rememberLazyListState()
|
||||
val swipeableState = rememberSwipeableState(if (isSearchOpen) Page.Search else Page.Widgets)
|
||||
|
||||
val isSearchAtStart by remember {
|
||||
derivedStateOf {
|
||||
searchState.firstVisibleItemIndex == 0 && searchState.firstVisibleItemScrollOffset == 0
|
||||
}
|
||||
}
|
||||
|
||||
val isSearchAtEnd by remember {
|
||||
derivedStateOf {
|
||||
val lastItem = searchState.layoutInfo.visibleItemsInfo.last()
|
||||
lastItem.offset + lastItem.size <= searchState.layoutInfo.viewportEndOffset - searchState.layoutInfo.afterContentPadding
|
||||
}
|
||||
}
|
||||
|
||||
val showStatusBarScrim by remember {
|
||||
derivedStateOf {
|
||||
if (isSearchOpen) {
|
||||
searchScrollState.value < searchScrollState.maxValue
|
||||
!isSearchAtEnd
|
||||
} else {
|
||||
widgetsScrollState.value > 0
|
||||
}
|
||||
@ -76,7 +89,7 @@ fun PagerScaffold(
|
||||
val showNavBarScrim by remember {
|
||||
derivedStateOf {
|
||||
if (isSearchOpen) {
|
||||
searchScrollState.value > 0
|
||||
!isSearchAtStart
|
||||
} else {
|
||||
widgetsScrollState.value > 0 && widgetsScrollState.value < widgetsScrollState.maxValue
|
||||
}
|
||||
@ -285,17 +298,21 @@ fun PagerScaffold(
|
||||
val webSearchPadding by animateDpAsState(
|
||||
if (websearches.isEmpty()) 0.dp else 48.dp
|
||||
)
|
||||
val windowInsets = WindowInsets.safeDrawing.asPaddingValues()
|
||||
SearchColumn(
|
||||
modifier = Modifier
|
||||
.requiredWidth(width)
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(searchScrollState, reverseScrolling = true)
|
||||
.imePadding()
|
||||
.windowInsetsPadding(WindowInsets.safeDrawing)
|
||||
.padding(horizontal = 8.dp)
|
||||
.padding(top = 8.dp, bottom = 64.dp)
|
||||
.padding(bottom = webSearchPadding),
|
||||
.padding(
|
||||
start = windowInsets.calculateStartPadding(LocalLayoutDirection.current),
|
||||
end = windowInsets.calculateStartPadding(LocalLayoutDirection.current),
|
||||
),
|
||||
reverse = true,
|
||||
state = searchState,
|
||||
paddingValues = PaddingValues(
|
||||
top = 4.dp + windowInsets.calculateTopPadding(),
|
||||
bottom = 60.dp + webSearchPadding + windowInsets.calculateBottomPadding()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -306,6 +323,7 @@ fun PagerScaffold(
|
||||
exit = slideOut { IntOffset(0, -it.height) }
|
||||
) {
|
||||
CenterAlignedTopAppBar(
|
||||
modifier = Modifier.systemBarsPadding(),
|
||||
title = {
|
||||
Text(stringResource(R.string.menu_edit_widgets))
|
||||
},
|
||||
@ -322,7 +340,7 @@ fun PagerScaffold(
|
||||
when {
|
||||
swipeableState.direction != 0f -> SearchBarLevel.Raised
|
||||
!isSearchOpen && isWidgetsScrollZero -> SearchBarLevel.Resting
|
||||
isSearchOpen && searchScrollState.value == 0 -> SearchBarLevel.Active
|
||||
isSearchOpen && isSearchAtStart -> SearchBarLevel.Active
|
||||
else -> SearchBarLevel.Raised
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,9 +6,9 @@ import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.slideIn
|
||||
import androidx.compose.animation.slideOut
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.LocalOverscrollConfiguration
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
@ -25,8 +25,8 @@ import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.Velocity
|
||||
@ -41,7 +41,6 @@ import de.mm20.launcher2.ui.launcher.search.SearchBarLevel
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchColumn
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchVM
|
||||
import de.mm20.launcher2.ui.launcher.widgets.WidgetColumn
|
||||
import de.mm20.launcher2.ui.modifier.verticalFadingEdges
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@ -60,20 +59,39 @@ fun PullDownScaffold(
|
||||
val isWidgetEditMode by viewModel.isWidgetEditMode.observeAsState(false)
|
||||
|
||||
val widgetsScrollState = rememberScrollState()
|
||||
val searchScrollState = rememberScrollState()
|
||||
val searchState = rememberLazyListState()
|
||||
|
||||
val isSearchAtStart by remember {
|
||||
derivedStateOf {
|
||||
searchState.firstVisibleItemIndex == 0 && searchState.firstVisibleItemScrollOffset == 0
|
||||
}
|
||||
}
|
||||
|
||||
val isSearchAtEnd by remember {
|
||||
derivedStateOf {
|
||||
val lastItem = searchState.layoutInfo.visibleItemsInfo.last()
|
||||
lastItem.offset + lastItem.size <= searchState.layoutInfo.viewportEndOffset - searchState.layoutInfo.afterContentPadding
|
||||
}
|
||||
}
|
||||
|
||||
val systemUiController = rememberSystemUiController()
|
||||
|
||||
val isWidgetsScrollZero by remember {
|
||||
val isWidgetsAtStart by remember {
|
||||
derivedStateOf {
|
||||
widgetsScrollState.value == 0
|
||||
}
|
||||
}
|
||||
|
||||
val isWidgetsAtEnd by remember {
|
||||
derivedStateOf {
|
||||
widgetsScrollState.value >= widgetsScrollState.maxValue
|
||||
}
|
||||
}
|
||||
|
||||
val showStatusBarScrim by remember {
|
||||
derivedStateOf {
|
||||
if (isSearchOpen) {
|
||||
searchScrollState.value > 0
|
||||
!isSearchAtStart
|
||||
} else {
|
||||
widgetsScrollState.value > 0
|
||||
}
|
||||
@ -82,7 +100,7 @@ fun PullDownScaffold(
|
||||
val showNavBarScrim by remember {
|
||||
derivedStateOf {
|
||||
if (isSearchOpen) {
|
||||
searchScrollState.value < searchScrollState.maxValue
|
||||
!isSearchAtEnd
|
||||
} else {
|
||||
widgetsScrollState.value > 0 && widgetsScrollState.value < widgetsScrollState.maxValue
|
||||
}
|
||||
@ -146,7 +164,7 @@ fun PullDownScaffold(
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
LaunchedEffect(isSearchOpen) {
|
||||
if (isSearchOpen) searchScrollState.scrollTo(0)
|
||||
if (isSearchOpen) searchState.scrollToItem(0)
|
||||
if (!isSearchOpen) searchVM.search("")
|
||||
searchBarOffset.animateTo(0f)
|
||||
}
|
||||
@ -176,22 +194,25 @@ fun PullDownScaffold(
|
||||
object : NestedScrollConnection {
|
||||
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
|
||||
if (isWidgetEditMode) return Offset.Zero
|
||||
val value = if (isSearchOpen) searchScrollState.value else widgetsScrollState.value
|
||||
val newValue = value - available.y
|
||||
val canPullDown = if (isSearchOpen) {
|
||||
isSearchAtStart
|
||||
} else {
|
||||
isWidgetsAtStart
|
||||
}
|
||||
val canPullUp = isSearchOpen && isSearchAtEnd
|
||||
|
||||
val consumed = when {
|
||||
(offsetY.value > 0 || source == NestedScrollSource.Drag && newValue < 0) -> {
|
||||
val consumed = available.y - value
|
||||
offsetY.value = (offsetY.value + (consumed * 0.5f)).coerceIn(0f, maxOffset)
|
||||
consumed
|
||||
}
|
||||
isSearchOpen && (offsetY.value < 0 || source == NestedScrollSource.Drag && newValue > searchScrollState.maxValue) -> {
|
||||
val consumed = available.y - (value - searchScrollState.maxValue)
|
||||
canPullUp && available.y < 0 || offsetY.value < 0 -> {
|
||||
val consumed = available.y
|
||||
offsetY.value = (offsetY.value + (consumed * 0.5f)).coerceIn(-maxOffset, 0f)
|
||||
consumed
|
||||
}
|
||||
else -> {
|
||||
0f
|
||||
canPullDown && available.y > 0 || offsetY.value > 0 -> {
|
||||
val consumed = available.y
|
||||
offsetY.value = (offsetY.value + (consumed * 0.5f)).coerceIn(0f, maxOffset)
|
||||
consumed
|
||||
}
|
||||
else -> 0f
|
||||
}
|
||||
|
||||
searchBarOffset.value =
|
||||
@ -246,11 +267,11 @@ fun PullDownScaffold(
|
||||
)
|
||||
}
|
||||
) {
|
||||
|
||||
val websearches by searchVM.websearchResults.observeAsState(emptyList())
|
||||
val webSearchPadding by animateDpAsState(
|
||||
if (websearches.isEmpty()) 0.dp else 48.dp
|
||||
)
|
||||
val windowInsets = WindowInsets.safeDrawing.asPaddingValues()
|
||||
SearchColumn(
|
||||
modifier = Modifier
|
||||
.graphicsLayer {
|
||||
@ -261,16 +282,20 @@ fun PullDownScaffold(
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.requiredHeight(height)
|
||||
.verticalScroll(searchScrollState)
|
||||
.windowInsetsPadding(WindowInsets.safeDrawing)
|
||||
.padding(8.dp)
|
||||
.padding(top = 56.dp)
|
||||
.padding(top = webSearchPadding)
|
||||
.imePadding()
|
||||
.padding(
|
||||
start = windowInsets.calculateStartPadding(LocalLayoutDirection.current),
|
||||
end = windowInsets.calculateStartPadding(LocalLayoutDirection.current),
|
||||
),
|
||||
paddingValues = PaddingValues(
|
||||
top = 60.dp + webSearchPadding + windowInsets.calculateTopPadding(),
|
||||
bottom = 4.dp + windowInsets.calculateBottomPadding()
|
||||
),
|
||||
state = searchState,
|
||||
|
||||
)
|
||||
val editModePadding by animateDpAsState(if (isWidgetEditMode) 56.dp else 0.dp)
|
||||
val clockPadding by animateDpAsState(
|
||||
if (isWidgetsScrollZero) insets.calculateBottomPadding() else 0.dp
|
||||
if (isWidgetsAtStart) insets.calculateBottomPadding() else 0.dp
|
||||
)
|
||||
val clockHeight by remember {
|
||||
derivedStateOf {
|
||||
@ -328,9 +353,9 @@ fun PullDownScaffold(
|
||||
derivedStateOf {
|
||||
when {
|
||||
offsetY.value != 0f -> SearchBarLevel.Raised
|
||||
isSearchOpen && searchScrollState.value == 0 -> SearchBarLevel.Active
|
||||
isSearchOpen && searchScrollState.value > 0 -> SearchBarLevel.Raised
|
||||
!isWidgetsScrollZero -> SearchBarLevel.Raised
|
||||
isSearchOpen && isSearchAtStart -> SearchBarLevel.Active
|
||||
isSearchOpen && !isSearchAtStart -> SearchBarLevel.Raised
|
||||
!isWidgetsAtStart -> SearchBarLevel.Raised
|
||||
else -> SearchBarLevel.Resting
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,41 +1,243 @@
|
||||
package de.mm20.launcher2.ui.launcher.search
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.CornerSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import de.mm20.launcher2.ui.launcher.search.apps.AppResults
|
||||
import de.mm20.launcher2.ui.launcher.search.appshortcuts.AppShortcutResults
|
||||
import de.mm20.launcher2.ui.launcher.search.calculator.CalculatorResults
|
||||
import de.mm20.launcher2.ui.launcher.search.calendar.CalendarResults
|
||||
import de.mm20.launcher2.ui.launcher.search.contacts.ContactResults
|
||||
import de.mm20.launcher2.ui.launcher.search.favorites.FavoritesResults
|
||||
import de.mm20.launcher2.ui.launcher.search.files.FileResults
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.component.LauncherCard
|
||||
import de.mm20.launcher2.ui.launcher.search.calculator.CalculatorItem
|
||||
import de.mm20.launcher2.ui.launcher.search.common.grid.GridItem
|
||||
import de.mm20.launcher2.ui.launcher.search.common.list.ListItem
|
||||
import de.mm20.launcher2.ui.launcher.search.hidden.HiddenResults
|
||||
import de.mm20.launcher2.ui.launcher.search.unitconverter.UnitConverterResults
|
||||
import de.mm20.launcher2.ui.launcher.search.website.WebsiteResults
|
||||
import de.mm20.launcher2.ui.launcher.search.wikipedia.WikipediaResults
|
||||
import de.mm20.launcher2.ui.layout.BottomReversed
|
||||
import de.mm20.launcher2.ui.launcher.search.unitconverter.UnitConverterItem
|
||||
import de.mm20.launcher2.ui.launcher.search.website.WebsiteItem
|
||||
import de.mm20.launcher2.ui.launcher.search.wikipedia.WikipediaItem
|
||||
import de.mm20.launcher2.ui.locals.LocalGridColumns
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlin.math.ceil
|
||||
|
||||
@Composable
|
||||
fun SearchColumn(
|
||||
modifier: Modifier = Modifier,
|
||||
paddingValues: PaddingValues = PaddingValues(0.dp),
|
||||
state: LazyListState = rememberLazyListState(),
|
||||
reverse: Boolean = false,
|
||||
) {
|
||||
Column(
|
||||
|
||||
val columns = LocalGridColumns.current
|
||||
|
||||
val viewModel: SearchVM = viewModel()
|
||||
|
||||
val hideFavs by viewModel.hideFavorites.observeAsState(true)
|
||||
val favorites by viewModel.favorites.observeAsState(emptyList())
|
||||
val apps by viewModel.appResults.observeAsState(emptyList())
|
||||
val appShortcuts by viewModel.appShortcutResults.observeAsState(emptyList())
|
||||
val contacts by viewModel.contactResults.observeAsState(emptyList())
|
||||
val files by viewModel.fileResults.observeAsState(emptyList())
|
||||
val events by viewModel.calendarResults.observeAsState(emptyList())
|
||||
val unitConverter by viewModel.unitConverterResult.observeAsState(null)
|
||||
val calculator by viewModel.calculatorResult.observeAsState(null)
|
||||
val wikipedia by viewModel.wikipediaResult.observeAsState(null)
|
||||
val website by viewModel.websiteResult.observeAsState(null)
|
||||
|
||||
|
||||
LazyColumn(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
verticalArrangement = if (reverse) Arrangement.BottomReversed else Arrangement.Top
|
||||
contentPadding = paddingValues,
|
||||
reverseLayout = reverse,
|
||||
) {
|
||||
FavoritesResults(reverse)
|
||||
AppResults(reverse)
|
||||
AppShortcutResults(reverse)
|
||||
UnitConverterResults(reverse)
|
||||
CalculatorResults(reverse)
|
||||
CalendarResults(reverse)
|
||||
ContactResults(reverse)
|
||||
WikipediaResults(reverse)
|
||||
WebsiteResults(reverse)
|
||||
FileResults(reverse)
|
||||
HiddenResults()
|
||||
if (!hideFavs) {
|
||||
GridResults(favorites.toImmutableList(), columns, reverse)
|
||||
}
|
||||
GridResults(apps.toImmutableList(), columns, reverse)
|
||||
ListResults(appShortcuts.toImmutableList(), reverse)
|
||||
val uc = unitConverter
|
||||
if (uc != null) {
|
||||
SingleResult {
|
||||
UnitConverterItem(unitConverter = uc)
|
||||
}
|
||||
}
|
||||
val calc = calculator
|
||||
if (calc != null) {
|
||||
SingleResult {
|
||||
CalculatorItem(calculator = calc)
|
||||
}
|
||||
}
|
||||
ListResults(events.toImmutableList(), reverse)
|
||||
ListResults(contacts.toImmutableList(), reverse)
|
||||
val wiki = wikipedia
|
||||
if (wiki != null) {
|
||||
SingleResult {
|
||||
WikipediaItem(wikipedia = wiki)
|
||||
}
|
||||
}
|
||||
val ws = website
|
||||
if (ws != null) {
|
||||
SingleResult {
|
||||
WebsiteItem(website = ws)
|
||||
}
|
||||
}
|
||||
ListResults(files.toImmutableList(), reverse)
|
||||
item {
|
||||
HiddenResults()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun LazyListScope.GridResults(
|
||||
items: ImmutableList<Searchable>,
|
||||
columns: Int,
|
||||
reverse: Boolean,
|
||||
) {
|
||||
if (items.isEmpty()) return
|
||||
val rows = ceil(items.size / columns.toFloat()).toInt()
|
||||
items(rows) {
|
||||
GridRow(
|
||||
items = items.subList(it * columns, (it * columns + columns).coerceAtMost(items.size)),
|
||||
columns = columns,
|
||||
isFirst = if (reverse) it == rows - 1 else it == 0,
|
||||
isLast = if (reverse) it == 0 else it == rows - 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GridRow(
|
||||
modifier: Modifier = Modifier,
|
||||
items: ImmutableList<Searchable>,
|
||||
columns: Int,
|
||||
isFirst: Boolean,
|
||||
isLast: Boolean,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clipToBounds()
|
||||
) {
|
||||
LauncherCard(
|
||||
modifier = Modifier.padding(
|
||||
start = 8.dp,
|
||||
end = 8.dp,
|
||||
top = if (isFirst) 4.dp else 0.dp,
|
||||
bottom = if (isLast) 4.dp else 0.dp,
|
||||
),
|
||||
shape = when {
|
||||
isFirst && isLast -> MaterialTheme.shapes.medium
|
||||
isFirst -> MaterialTheme.shapes.medium.copy(
|
||||
bottomEnd = CornerSize(0),
|
||||
bottomStart = CornerSize(0),
|
||||
)
|
||||
isLast -> MaterialTheme.shapes.medium.copy(
|
||||
topEnd = CornerSize(0),
|
||||
topStart = CornerSize(0),
|
||||
)
|
||||
else -> RectangleShape
|
||||
}
|
||||
) {
|
||||
Row {
|
||||
for (item in items) {
|
||||
GridItem(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(4.dp, 8.dp),
|
||||
item = item,
|
||||
showLabels = true
|
||||
)
|
||||
}
|
||||
for (i in 0 until columns - items.size) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun LazyListScope.ListResults(
|
||||
items: ImmutableList<Searchable>,
|
||||
reverse: Boolean,
|
||||
) {
|
||||
if (items.isEmpty()) return
|
||||
items(items.size) {
|
||||
ListRow(
|
||||
item = items[it],
|
||||
isFirst = if (reverse) it == items.size - 1 else it == 0,
|
||||
isLast = if (reverse) it == 0 else it == items.size - 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ListRow(
|
||||
modifier: Modifier = Modifier,
|
||||
item: Searchable,
|
||||
isFirst: Boolean,
|
||||
isLast: Boolean,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clipToBounds()
|
||||
) {
|
||||
LauncherCard(
|
||||
modifier = Modifier.padding(
|
||||
start = 8.dp,
|
||||
end = 8.dp,
|
||||
top = if (isFirst) 4.dp else 0.dp,
|
||||
bottom = if (isLast) 4.dp else 0.dp,
|
||||
),
|
||||
shape = when {
|
||||
isFirst && isLast -> MaterialTheme.shapes.medium
|
||||
isFirst -> MaterialTheme.shapes.medium.copy(
|
||||
bottomEnd = CornerSize(0),
|
||||
bottomStart = CornerSize(0),
|
||||
)
|
||||
isLast -> MaterialTheme.shapes.medium.copy(
|
||||
topEnd = CornerSize(0),
|
||||
topStart = CornerSize(0),
|
||||
)
|
||||
else -> RectangleShape
|
||||
}
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.padding(
|
||||
start = 8.dp,
|
||||
end = 8.dp,
|
||||
top = if (isFirst) 8.dp else 4.dp,
|
||||
bottom = if (isLast) 8.dp else 4.dp,
|
||||
)
|
||||
) {
|
||||
ListItem(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
item = item
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun LazyListScope.SingleResult(content: @Composable (() -> Unit)?) {
|
||||
if (content == null) return
|
||||
item {
|
||||
LauncherCard(
|
||||
modifier = Modifier.padding(
|
||||
horizontal = 8.dp,
|
||||
vertical = 4.dp,
|
||||
)
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,7 +14,7 @@ import de.mm20.launcher2.ui.launcher.search.SearchVM
|
||||
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
|
||||
|
||||
@Composable
|
||||
fun ColumnScope.AppResults(reverse: Boolean = false) {
|
||||
fun AppResults(reverse: Boolean = false) {
|
||||
val viewModel: SearchVM = viewModel()
|
||||
val apps by viewModel.appResults.observeAsState(emptyList())
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ import de.mm20.launcher2.ui.launcher.search.SearchVM
|
||||
import de.mm20.launcher2.ui.launcher.search.common.grid.SearchResultGrid
|
||||
|
||||
@Composable
|
||||
fun ColumnScope.FavoritesResults(
|
||||
fun FavoritesResults(
|
||||
reverse: Boolean = false,
|
||||
) {
|
||||
val viewModel: SearchVM = viewModel()
|
||||
|
||||
@ -19,7 +19,7 @@ import de.mm20.launcher2.ui.launcher.modals.HiddenItemsSheet
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchVM
|
||||
|
||||
@Composable
|
||||
fun ColumnScope.HiddenResults() {
|
||||
fun HiddenResults() {
|
||||
val viewModel: SearchVM = viewModel()
|
||||
val hiddenResults by viewModel.hiddenResults.observeAsState(
|
||||
emptyList()
|
||||
@ -27,7 +27,7 @@ fun ColumnScope.HiddenResults() {
|
||||
|
||||
var showHiddenItems by remember { mutableStateOf(false) }
|
||||
|
||||
AnimatedVisibility(visible = hiddenResults.isNotEmpty()) {
|
||||
if(hiddenResults.isNotEmpty()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user