Improve pager scaffold performance
This commit is contained in:
parent
c922d62228
commit
764aa3e587
@ -7,38 +7,46 @@ 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.gestures.LocalOverScrollConfiguration
|
||||||
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
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.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Done
|
import androidx.compose.material.icons.rounded.Done
|
||||||
|
import androidx.compose.material.rememberSwipeableState
|
||||||
|
import androidx.compose.material.swipeable
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
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.IntSize
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
import com.google.accompanist.pager.HorizontalPager
|
|
||||||
import com.google.accompanist.pager.rememberPagerState
|
|
||||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.ktx.toPixels
|
||||||
import de.mm20.launcher2.ui.launcher.search.SearchBar
|
import de.mm20.launcher2.ui.launcher.search.SearchBar
|
||||||
import de.mm20.launcher2.ui.launcher.search.SearchBarLevel
|
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 kotlin.math.roundToInt
|
||||||
|
|
||||||
@OptIn(ExperimentalPagerApi::class)
|
@OptIn(
|
||||||
|
ExperimentalPagerApi::class, ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class,
|
||||||
|
ExperimentalFoundationApi::class
|
||||||
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun PagerScaffold(
|
fun PagerScaffold(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
@ -52,9 +60,9 @@ fun PagerScaffold(
|
|||||||
val isSearchOpen by viewModel.isSearchOpen.observeAsState(false)
|
val isSearchOpen by viewModel.isSearchOpen.observeAsState(false)
|
||||||
val isWidgetEditMode by viewModel.isWidgetEditMode.observeAsState(false)
|
val isWidgetEditMode by viewModel.isWidgetEditMode.observeAsState(false)
|
||||||
|
|
||||||
val pagerState = rememberPagerState()
|
|
||||||
val widgetsScrollState = rememberScrollState()
|
val widgetsScrollState = rememberScrollState()
|
||||||
val searchScrollState = rememberScrollState()
|
val searchScrollState = rememberScrollState()
|
||||||
|
val swipeableState = rememberSwipeableState(if (isSearchOpen) Page.Search else Page.Widgets)
|
||||||
|
|
||||||
val isWidgetsScrollZero by remember {
|
val isWidgetsScrollZero by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
@ -88,8 +96,8 @@ fun PagerScaffold(
|
|||||||
|
|
||||||
val blurWallpaper by remember {
|
val blurWallpaper by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
isSearchOpen || pagerState.currentPage == 0 && pagerState.currentPageOffset > 0.5f ||
|
isSearchOpen || swipeableState.progress.to == Page.Widgets && swipeableState.progress.fraction <= 0.5f ||
|
||||||
pagerState.currentPage == 1 && pagerState.currentPageOffset <= 0.5f ||
|
swipeableState.progress.to == Page.Search && swipeableState.progress.fraction > 0.5f ||
|
||||||
!isWidgetsScrollZero
|
!isWidgetsScrollZero
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,6 +105,7 @@ fun PagerScaffold(
|
|||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
LaunchedEffect(blurWallpaper) {
|
LaunchedEffect(blurWallpaper) {
|
||||||
if (!isAtLeastApiLevel(31)) return@LaunchedEffect
|
if (!isAtLeastApiLevel(31)) return@LaunchedEffect
|
||||||
(context as Activity).window.attributes = context.window.attributes.also {
|
(context as Activity).window.attributes = context.window.attributes.also {
|
||||||
@ -110,16 +119,16 @@ fun PagerScaffold(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentPage = pagerState.currentPage
|
val currentPage = swipeableState.currentValue
|
||||||
LaunchedEffect(currentPage) {
|
LaunchedEffect(currentPage) {
|
||||||
if (currentPage == 1) viewModel.openSearch()
|
if (currentPage == Page.Search) viewModel.openSearch()
|
||||||
else viewModel.closeSearch()
|
else viewModel.closeSearch()
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(isSearchOpen) {
|
LaunchedEffect(isSearchOpen) {
|
||||||
if (isSearchOpen) pagerState.animateScrollToPage(1)
|
if (isSearchOpen) swipeableState.animateTo(Page.Search)
|
||||||
else {
|
else {
|
||||||
pagerState.animateScrollToPage(0)
|
swipeableState.animateTo(Page.Widgets)
|
||||||
searchVM.search("")
|
searchVM.search("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,64 +145,93 @@ fun PagerScaffold(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
) {
|
) {
|
||||||
|
|
||||||
HorizontalPager(
|
BoxWithConstraints(
|
||||||
count = 2,
|
modifier = Modifier.fillMaxWidth()
|
||||||
state = pagerState,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize(),
|
|
||||||
userScrollEnabled = !isWidgetEditMode
|
|
||||||
) {
|
) {
|
||||||
if (it == 1) {
|
val height by remember {
|
||||||
val websearches by searchVM.websearchResults.observeAsState(emptyList())
|
derivedStateOf { maxHeight }
|
||||||
val webSearchPadding by animateDpAsState(
|
}
|
||||||
if (websearches.isEmpty()) 0.dp else 48.dp
|
val width by remember {
|
||||||
)
|
derivedStateOf { maxWidth }
|
||||||
SearchColumn(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.verticalScroll(searchScrollState, reverseScrolling = true)
|
|
||||||
.imePadding()
|
|
||||||
.padding(start = 8.dp, end = 8.dp, top = 8.dp, bottom = 64.dp)
|
|
||||||
.padding(bottom = webSearchPadding),
|
|
||||||
reverse = true,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it == 0) {
|
|
||||||
val editModePadding by animateDpAsState(if (isWidgetEditMode) 56.dp else 0.dp)
|
|
||||||
|
|
||||||
val clockPadding by animateDpAsState(
|
val widthPx = width.toPixels()
|
||||||
if (isWidgetsScrollZero) 64.dp else 0.dp
|
|
||||||
)
|
|
||||||
var size by remember { mutableStateOf(IntSize.Zero) }
|
|
||||||
|
|
||||||
val clockHeight by remember {
|
|
||||||
derivedStateOf {
|
|
||||||
with(density) { size.height.toDp() } - (64.dp - clockPadding)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WidgetColumn(
|
CompositionLocalProvider(
|
||||||
|
LocalOverScrollConfiguration provides null
|
||||||
|
) {
|
||||||
|
|
||||||
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.requiredWidth(width * 2)
|
||||||
.onSizeChanged {
|
.fillMaxHeight()
|
||||||
size = it
|
.swipeable(
|
||||||
|
swipeableState,
|
||||||
|
orientation = Orientation.Horizontal,
|
||||||
|
anchors = mapOf(
|
||||||
|
-widthPx / 2f to Page.Search,
|
||||||
|
widthPx / 2f to Page.Widgets,
|
||||||
|
),
|
||||||
|
thresholds = { _, _ ->
|
||||||
|
FractionalThreshold(0.5f)
|
||||||
|
},
|
||||||
|
enabled = !isWidgetEditMode
|
||||||
|
)
|
||||||
|
.offset {
|
||||||
|
IntOffset(swipeableState.offset.value.roundToInt(), 0)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
val editModePadding by animateDpAsState(if (isWidgetEditMode) 56.dp else 0.dp)
|
||||||
|
|
||||||
|
val clockPadding by animateDpAsState(
|
||||||
|
if (isWidgetsScrollZero) 64.dp else 0.dp
|
||||||
|
)
|
||||||
|
|
||||||
|
val clockHeight by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
height - (64.dp - clockPadding)
|
||||||
}
|
}
|
||||||
.verticalScroll(widgetsScrollState)
|
|
||||||
.padding(start = 8.dp, end = 8.dp, top = 8.dp, bottom = 64.dp)
|
|
||||||
.padding(top = editModePadding),
|
|
||||||
clockHeight = { clockHeight },
|
|
||||||
clockBottomPadding = { clockPadding },
|
|
||||||
editMode = isWidgetEditMode,
|
|
||||||
onEditModeChange = {
|
|
||||||
viewModel.setWidgetEditMode(it)
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
WidgetColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.requiredWidth(width)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.verticalScroll(widgetsScrollState)
|
||||||
|
.padding(start = 8.dp, end = 8.dp, top = 8.dp, bottom = 64.dp)
|
||||||
|
.padding(top = editModePadding),
|
||||||
|
clockHeight = { clockHeight },
|
||||||
|
clockBottomPadding = { clockPadding },
|
||||||
|
editMode = isWidgetEditMode,
|
||||||
|
onEditModeChange = {
|
||||||
|
viewModel.setWidgetEditMode(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
val websearches by searchVM.websearchResults.observeAsState(emptyList())
|
||||||
|
val webSearchPadding by animateDpAsState(
|
||||||
|
if (websearches.isEmpty()) 0.dp else 48.dp
|
||||||
|
)
|
||||||
|
SearchColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.requiredWidth(width)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.verticalScroll(searchScrollState, reverseScrolling = true)
|
||||||
|
.imePadding()
|
||||||
|
.padding(start = 8.dp, end = 8.dp, top = 8.dp, bottom = 64.dp)
|
||||||
|
.padding(bottom = webSearchPadding),
|
||||||
|
reverse = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AnimatedVisibility(visible = isWidgetEditMode,
|
AnimatedVisibility(visible = isWidgetEditMode,
|
||||||
@ -215,7 +253,7 @@ fun PagerScaffold(
|
|||||||
val searchBarLevel by remember {
|
val searchBarLevel by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
when {
|
when {
|
||||||
pagerState.isScrollInProgress -> SearchBarLevel.Raised
|
swipeableState.direction != 0f -> SearchBarLevel.Raised
|
||||||
!isSearchOpen && isWidgetsScrollZero -> SearchBarLevel.Resting
|
!isSearchOpen && isWidgetsScrollZero -> SearchBarLevel.Resting
|
||||||
isSearchOpen && searchScrollState.value == 0 -> SearchBarLevel.Active
|
isSearchOpen && searchScrollState.value == 0 -> SearchBarLevel.Active
|
||||||
else -> SearchBarLevel.Raised
|
else -> SearchBarLevel.Raised
|
||||||
@ -242,5 +280,9 @@ fun PagerScaffold(
|
|||||||
reverse = true
|
reverse = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class Page {
|
||||||
|
Widgets,
|
||||||
|
Search
|
||||||
}
|
}
|
||||||
@ -216,7 +216,6 @@ fun PullDownScaffold(
|
|||||||
val height by remember {
|
val height by remember {
|
||||||
derivedStateOf { maxHeight }
|
derivedStateOf { maxHeight }
|
||||||
}
|
}
|
||||||
Log.d("MM20", "PullDownScaffold recompose, $maxHeight")
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalOverScrollConfiguration provides null
|
LocalOverScrollConfiguration provides null
|
||||||
) {
|
) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user