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