Use pager for pull down scaffold

This commit is contained in:
MM20 2023-01-19 19:09:04 +01:00
parent db28ce32ca
commit dfc52a3c38
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389

View File

@ -2,8 +2,9 @@ package de.mm20.launcher2.ui.launcher
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.Spring
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring
import androidx.compose.animation.slideIn import androidx.compose.animation.slideIn
import androidx.compose.animation.slideOut import androidx.compose.animation.slideOut
import androidx.compose.foundation.LocalOverscrollConfiguration import androidx.compose.foundation.LocalOverscrollConfiguration
@ -21,11 +22,12 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.VerticalPager
import androidx.compose.foundation.pager.rememberPagerState
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
@ -100,11 +102,14 @@ fun PullDownScaffold(
val widgetsScrollState = rememberScrollState() val widgetsScrollState = rememberScrollState()
val searchState = rememberLazyListState() val searchState = rememberLazyListState()
val pagerState = rememberPagerState()
val isSearchAtTop by remember { val isSearchAtTop by remember {
derivedStateOf { derivedStateOf {
if (reverseSearchResults) { if (reverseSearchResults) {
val lastItem = val lastItem =
searchState.layoutInfo.visibleItemsInfo.lastOrNull() ?: return@derivedStateOf true searchState.layoutInfo.visibleItemsInfo.lastOrNull()
?: return@derivedStateOf true
lastItem.offset + lastItem.size <= searchState.layoutInfo.viewportEndOffset - searchState.layoutInfo.afterContentPadding lastItem.offset + lastItem.size <= searchState.layoutInfo.viewportEndOffset - searchState.layoutInfo.afterContentPadding
} else { } else {
searchState.firstVisibleItemIndex == 0 && searchState.firstVisibleItemScrollOffset == 0 searchState.firstVisibleItemIndex == 0 && searchState.firstVisibleItemScrollOffset == 0
@ -118,7 +123,8 @@ fun PullDownScaffold(
searchState.firstVisibleItemIndex == 0 && searchState.firstVisibleItemScrollOffset == 0 searchState.firstVisibleItemIndex == 0 && searchState.firstVisibleItemScrollOffset == 0
} else { } else {
val lastItem = val lastItem =
searchState.layoutInfo.visibleItemsInfo.lastOrNull() ?: return@derivedStateOf true searchState.layoutInfo.visibleItemsInfo.lastOrNull()
?: return@derivedStateOf true
lastItem.offset + lastItem.size <= searchState.layoutInfo.viewportEndOffset - searchState.layoutInfo.afterContentPadding lastItem.offset + lastItem.size <= searchState.layoutInfo.viewportEndOffset - searchState.layoutInfo.afterContentPadding
} }
} }
@ -216,9 +222,22 @@ fun PullDownScaffold(
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
LaunchedEffect(isSearchOpen) { LaunchedEffect(isSearchOpen) {
if (isSearchOpen) searchState.scrollToItem(0) launch {
if (!isSearchOpen) searchVM.search("") searchBarOffset.animateTo(0f)
searchBarOffset.animateTo(0f) }
if (isSearchOpen) {
searchState.scrollToItem(0)
pagerState.animateScrollToPage(
1,
animationSpec = spring(stiffness = Spring.StiffnessMediumLow)
)
} else {
searchVM.search("")
pagerState.animateScrollToPage(
0,
animationSpec = spring(stiffness = Spring.StiffnessMediumLow)
)
}
} }
LaunchedEffect(isWidgetEditMode) { LaunchedEffect(isWidgetEditMode) {
@ -250,8 +269,8 @@ fun PullDownScaffold(
val nestedScrollConnection = remember { val nestedScrollConnection = remember {
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 || source != NestedScrollSource.Drag) return Offset.Zero
if (source == NestedScrollSource.Drag && available.y.absoluteValue > available.x.absoluteValue * 2) { if (available.y.absoluteValue > available.x.absoluteValue * 2) {
keyboardController?.hide() keyboardController?.hide()
} }
val canPullDown = if (isSearchOpen) { val canPullDown = if (isSearchOpen) {
@ -285,8 +304,10 @@ fun PullDownScaffold(
available: Offset, available: Offset,
source: NestedScrollSource source: NestedScrollSource
): Offset { ): Offset {
val deltaSearchBarOffset = consumed.y * if (isSearchOpen && reverseSearchResults) 1 else -1 val deltaSearchBarOffset =
searchBarOffset.value = (searchBarOffset.value + deltaSearchBarOffset).coerceIn(0f, maxSearchBarOffset) consumed.y * if (isSearchOpen && reverseSearchResults) 1 else -1
searchBarOffset.value =
(searchBarOffset.value + deltaSearchBarOffset).coerceIn(0f, maxSearchBarOffset)
return super.onPostScroll(consumed, available, source) return super.onPostScroll(consumed, available, source)
} }
@ -321,7 +342,6 @@ fun PullDownScaffold(
.offset { IntOffset(0, offsetY.value.toInt()) }, .offset { IntOffset(0, offsetY.value.toInt()) },
contentAlignment = Alignment.TopCenter contentAlignment = Alignment.TopCenter
) { ) {
BoxWithConstraints( BoxWithConstraints(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
@ -332,109 +352,114 @@ fun PullDownScaffold(
CompositionLocalProvider( CompositionLocalProvider(
LocalOverscrollConfiguration provides null LocalOverscrollConfiguration provides null
) { ) {
val offset by animateFloatAsState(if (isSearchOpen) 1f else 0f) VerticalPager(
Column( modifier = Modifier.fillMaxSize(),
modifier = Modifier pageCount = 2,
.fillMaxWidth() beyondBoundsPageCount = 1,
.requiredHeight(height * 2) state = pagerState,
.offset { reverseLayout = true,
IntOffset( userScrollEnabled = false,
0,
((-0.5f + offset) * height.toPx()).roundToInt()
)
}
) { ) {
val webSearchPadding by animateDpAsState( when (it) {
if (actions.isEmpty()) 0.dp else 48.dp
)
val windowInsets = WindowInsets.safeDrawing.asPaddingValues()
SearchColumn(
modifier = Modifier
.graphicsLayer {
transformOrigin = TransformOrigin.Center
scaleX = offset
scaleY = offset
alpha = offset
}
.fillMaxWidth()
.requiredHeight(height)
.padding(
start = windowInsets.calculateStartPadding(LocalLayoutDirection.current),
end = windowInsets.calculateStartPadding(LocalLayoutDirection.current),
),
paddingValues = PaddingValues(
top = windowInsets.calculateTopPadding() + if (!bottomSearchBar) 60.dp + webSearchPadding else 4.dp,
bottom = windowInsets.calculateBottomPadding() + if (bottomSearchBar) 60.dp + webSearchPadding else 4.dp
),
state = searchState,
reverse = reverseSearchResults,
)
val clockPadding by animateDpAsState(
if (isWidgetsAtStart && fillClockHeight)
insets.calculateBottomPadding() + if (bottomSearchBar) 64.dp else 0.dp
else 0.dp
) 0 -> {
val clockHeight by remember { val clockPadding by animateDpAsState(
derivedStateOf { if (isWidgetsAtStart && fillClockHeight)
if (fillClockHeight) { insets.calculateBottomPadding() + if (bottomSearchBar) 64.dp else 0.dp
height - (insets.calculateTopPadding() + insets.calculateBottomPadding() - clockPadding + 56.dp) else 0.dp
} else {
null
}
}
}
Column( )
modifier = Modifier val clockHeight by remember {
.graphicsLayer { derivedStateOf {
transformOrigin = TransformOrigin.Center if (fillClockHeight) {
scaleX = 1 - offset height - (insets.calculateTopPadding() + insets.calculateBottomPadding() - clockPadding + 56.dp)
scaleY = 1 - offset } else {
alpha = 1 - offset null
}
}
} }
.pointerInput(Unit) { Column(
detectTapGestures( modifier = Modifier
onDoubleTap = { .graphicsLayer {
gestureManager.dispatchDoubleTap(it) val progress = pagerState.currentPage + pagerState.currentPageOffsetFraction
}, transformOrigin = TransformOrigin.Center
onLongPress = { alpha = 1 - progress
gestureManager.dispatchLongPress(it) }
.pointerInput(Unit) {
detectTapGestures(
onDoubleTap = {
gestureManager.dispatchDoubleTap(it)
},
onLongPress = {
gestureManager.dispatchLongPress(it)
}
)
}
.fillMaxSize()
.verticalScroll(widgetsScrollState)
.windowInsetsPadding(WindowInsets.safeDrawing)
.padding(8.dp)
.padding(
top = if (bottomSearchBar) 0.dp else 56.dp,
bottom = if (bottomSearchBar) 56.dp else 0.dp,
)
) {
AnimatedVisibility(!isWidgetEditMode) {
Box(
modifier = Modifier
.fillMaxWidth()
.then(clockHeight?.let { Modifier.height(it) }
?: Modifier)
.padding(bottom = clockPadding),
contentAlignment = Alignment.BottomCenter
) {
ClockWidget(
modifier = Modifier.fillMaxWidth()
)
}
}
WidgetColumn(
editMode = isWidgetEditMode,
onEditModeChange = {
viewModel.setWidgetEditMode(it)
} }
) )
} }
.fillMaxWidth()
.requiredHeight(height)
.verticalScroll(widgetsScrollState)
.windowInsetsPadding(WindowInsets.safeDrawing)
.padding(8.dp)
.padding(
top = if (bottomSearchBar) 0.dp else 56.dp,
bottom = if (bottomSearchBar) 56.dp else 0.dp,
)
) {
AnimatedVisibility(!isWidgetEditMode) {
Box(
modifier = Modifier
.fillMaxWidth()
.then(clockHeight?.let { Modifier.height(it) } ?: Modifier)
.padding(bottom = clockPadding),
contentAlignment = Alignment.BottomCenter
) {
ClockWidget(
modifier = Modifier.fillMaxWidth()
)
}
} }
WidgetColumn(
editMode = isWidgetEditMode, 1 -> {
onEditModeChange = { val webSearchPadding by animateDpAsState(
viewModel.setWidgetEditMode(it) if (actions.isEmpty()) 0.dp else 48.dp
} )
) val windowInsets = WindowInsets.safeDrawing.asPaddingValues()
SearchColumn(
modifier = Modifier
.graphicsLayer {
val progress = pagerState.currentPage + pagerState.currentPageOffsetFraction
transformOrigin = TransformOrigin.Center
alpha = progress
}
.fillMaxSize()
.padding(
start = windowInsets.calculateStartPadding(
LocalLayoutDirection.current
),
end = windowInsets.calculateStartPadding(
LocalLayoutDirection.current
),
),
paddingValues = PaddingValues(
top = windowInsets.calculateTopPadding() + if (!bottomSearchBar) 60.dp + webSearchPadding else 4.dp,
bottom = windowInsets.calculateBottomPadding() + if (bottomSearchBar) 60.dp + webSearchPadding else 4.dp
),
state = searchState,
reverse = reverseSearchResults,
)
}
} }
} }
} }
} }