Skip to content

Commit f6affe2

Browse files
committed
Art: simplify app bar scroll behavior
1 parent 2ba97e6 commit f6affe2

File tree

4 files changed

+26
-34
lines changed

4 files changed

+26
-34
lines changed

api_viewing/src/main/kotlin/com/madness/collision/unit/api_viewing/ui/list/AppList.kt

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import androidx.compose.foundation.layout.calculateStartPadding
3434
import androidx.compose.foundation.layout.fillMaxSize
3535
import androidx.compose.foundation.layout.only
3636
import androidx.compose.foundation.lazy.LazyColumn
37+
import androidx.compose.foundation.lazy.grid.LazyGridState
3738
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
3839
import androidx.compose.foundation.lazy.items
3940
import androidx.compose.material.icons.Icons
@@ -42,6 +43,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
4243
import androidx.compose.material3.MaterialTheme
4344
import androidx.compose.material3.Scaffold
4445
import androidx.compose.material3.TopAppBarDefaults
46+
import androidx.compose.material3.TopAppBarState
4547
import androidx.compose.material3.rememberTopAppBarState
4648
import androidx.compose.runtime.Composable
4749
import androidx.compose.runtime.LaunchedEffect
@@ -53,6 +55,7 @@ import androidx.compose.runtime.remember
5355
import androidx.compose.runtime.setValue
5456
import androidx.compose.ui.Modifier
5557
import androidx.compose.ui.graphics.Color
58+
import androidx.compose.ui.input.nestedscroll.nestedScroll
5659
import androidx.compose.ui.platform.LocalDensity
5760
import androidx.compose.ui.platform.LocalLayoutDirection
5861
import androidx.compose.ui.unit.dp
@@ -260,8 +263,9 @@ fun AppList(eventHandler: AppListEventHandler, paddingValues: PaddingValues) {
260263
AppListScaffold(
261264
listState = rememberAppListState(viewModel),
262265
eventHandler = rememberCompOptionsEventHandler(viewModel),
266+
appBarState = appBarState,
267+
listScrollState = scrollState,
263268
paddingValues = paddingValues,
264-
contentOffsetProgress = { -headerState.headerOffsetY },
265269
onAppBarOpacityChange = eventHandler::onAppBarOpacityChange,
266270
) { contentPadding ->
267271
AppListGrid(
@@ -303,33 +307,43 @@ interface AppListState {
303307
private fun AppListScaffold(
304308
listState: AppListState,
305309
eventHandler: CompositeOptionsEventHandler,
306-
paddingValues: PaddingValues,
307-
contentOffsetProgress: () -> Int,
310+
appBarState: TopAppBarState = rememberTopAppBarState(),
311+
listScrollState: LazyGridState = rememberLazyGridState(),
312+
paddingValues: PaddingValues = PaddingValues(),
308313
onAppBarOpacityChange: (Float) -> Unit = {},
309314
content: @Composable (PaddingValues) -> Unit
310315
) {
311316
var showListOptions by remember { mutableIntStateOf(0) }
317+
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(appBarState)
312318
Scaffold(
319+
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
313320
topBar = {
314321
val density = LocalDensity.current
315-
val toolbarOpacity by remember(density) {
322+
val toolbarOpacity by remember(density, appBarState) {
316323
val toolbarHeight = with(density) { 100.dp.toPx() }
317324
val headerHeight = (toolbarHeight * 1.8f).roundToInt()
318325
derivedStateOf {
319-
((contentOffsetProgress() - headerHeight) / toolbarHeight)
320-
.coerceIn(0f, 0.96f).mapIf({ it <= 0.06f }, { 0f })
326+
val fraction = if (listScrollState.firstVisibleItemIndex == 0) {
327+
// use first item offset when scrolled to top
328+
(listScrollState.firstVisibleItemScrollOffset - headerHeight) / toolbarHeight
329+
} else {
330+
(-appBarState.contentOffset - headerHeight) / toolbarHeight
331+
}
332+
fraction.coerceIn(0f, 0.96f).mapIf({ it <= 0.06f }, { 0f })
321333
}
322334
}
323335
LaunchedEffect(toolbarOpacity) {
324336
onAppBarOpacityChange(toolbarOpacity)
325337
}
326338

339+
val containerColor = MaterialTheme.colorScheme.surface.copy(alpha = toolbarOpacity)
327340
AppListBar(
328341
isRefreshing = listState.isRefreshing,
329342
windowInsets = paddingValues.asInsets()
330343
.only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top),
331344
colors = TopAppBarDefaults.topAppBarColors(
332-
containerColor = MaterialTheme.colorScheme.surface.copy(alpha = toolbarOpacity))
345+
containerColor = containerColor, scrolledContainerColor = containerColor),
346+
scrollBehavior = scrollBehavior,
333347
) {
334348
AppListBarAction(
335349
icon = Icons.Outlined.CheckCircle,
@@ -358,6 +372,7 @@ private fun AppListScaffold(
358372
)
359373
}
360374

375+
@OptIn(ExperimentalMaterial3Api::class)
361376
@PreviewCombinedColorLayout
362377
@Composable
363378
private fun AppListPreview() {
@@ -373,8 +388,6 @@ private fun AppListPreview() {
373388
AppListScaffold(
374389
listState = listState,
375390
eventHandler = remember { PseudoCompOptionsEventHandler() },
376-
paddingValues = PaddingValues(),
377-
contentOffsetProgress = { 50 },
378391
content = { _ -> Box(Modifier.fillMaxSize().background(Color.DarkGray)) }
379392
)
380393
}

api_viewing/src/main/kotlin/com/madness/collision/unit/api_viewing/ui/list/AppListAppBar.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import androidx.compose.material3.Text
5050
import androidx.compose.material3.TopAppBar
5151
import androidx.compose.material3.TopAppBarColors
5252
import androidx.compose.material3.TopAppBarDefaults
53+
import androidx.compose.material3.TopAppBarScrollBehavior
5354
import androidx.compose.material3.minimumInteractiveComponentSize
5455
import androidx.compose.material3.ripple
5556
import androidx.compose.runtime.Composable
@@ -74,6 +75,7 @@ fun AppListBar(
7475
isRefreshing: Boolean,
7576
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets,
7677
colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(),
78+
scrollBehavior: TopAppBarScrollBehavior? = null,
7779
primaryAction: @Composable () -> Unit,
7880
) {
7981
TopAppBar(
@@ -110,6 +112,7 @@ fun AppListBar(
110112
},
111113
windowInsets = windowInsets,
112114
colors = colors,
115+
scrollBehavior = scrollBehavior,
113116
)
114117
}
115118

api_viewing/src/main/kotlin/com/madness/collision/unit/api_viewing/ui/list/AppListHeader.kt

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@
1717
package com.madness.collision.unit.api_viewing.ui.list
1818

1919
import androidx.activity.compose.BackHandler
20-
import androidx.compose.animation.core.LinearEasing
21-
import androidx.compose.animation.core.animateIntOffsetAsState
22-
import androidx.compose.animation.core.tween
2320
import androidx.compose.foundation.background
2421
import androidx.compose.foundation.border
2522
import androidx.compose.foundation.clickable
@@ -31,7 +28,6 @@ import androidx.compose.foundation.layout.Spacer
3128
import androidx.compose.foundation.layout.fillMaxHeight
3229
import androidx.compose.foundation.layout.fillMaxWidth
3330
import androidx.compose.foundation.layout.height
34-
import androidx.compose.foundation.layout.offset
3531
import androidx.compose.foundation.layout.padding
3632
import androidx.compose.foundation.layout.size
3733
import androidx.compose.foundation.layout.width
@@ -64,14 +60,12 @@ import androidx.compose.ui.Modifier
6460
import androidx.compose.ui.draw.clip
6561
import androidx.compose.ui.graphics.Color
6662
import androidx.compose.ui.graphics.vector.ImageVector
67-
import androidx.compose.ui.layout.onSizeChanged
6863
import androidx.compose.ui.platform.LocalFocusManager
6964
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
7065
import androidx.compose.ui.res.stringResource
7166
import androidx.compose.ui.text.input.ImeAction
7267
import androidx.compose.ui.text.input.KeyboardType
7368
import androidx.compose.ui.text.style.TextOverflow
74-
import androidx.compose.ui.unit.IntOffset
7569
import androidx.compose.ui.unit.dp
7670
import androidx.compose.ui.unit.sp
7771
import com.madness.collision.chief.app.BoundoTheme
@@ -81,13 +75,10 @@ import racra.compose.smooth_corner_rect_library.AbsoluteSmoothCornerShape
8175

8276
@Stable
8377
interface ListHeaderState {
84-
var headerHeight: Int
85-
val headerOffsetY: Int
8678
val devInfoLabel: String
8779
val devInfoDesc: String
8880
var statsSize: Int
8981
fun setTerminalCat(cat: ListSrcCat)
90-
fun updateOffsetY(scrollY: Int)
9182
fun showStats(options: AppListOptions)
9283
fun showSystemModules()
9384
fun onQueryChange(query: String)
@@ -100,17 +91,7 @@ fun AppListSwitchHeader(
10091
appSrcState: AppSrcState,
10192
headerState: ListHeaderState,
10293
) {
103-
val headerOffset by animateIntOffsetAsState(
104-
targetValue = IntOffset(0, headerState.headerOffsetY),
105-
animationSpec = tween(easing = LinearEasing, durationMillis = 0),
106-
label = "HeaderOffsetAnim",
107-
)
108-
Column(
109-
modifier = Modifier
110-
.onSizeChanged { headerState.headerHeight = it.height }
111-
.offset { headerOffset }
112-
.then(modifier)
113-
) {
94+
Column(modifier = modifier) {
11495
var isQuerying: Boolean by remember { mutableStateOf(false) }
11596
AppListHeader(
11697
modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp),

api_viewing/src/main/kotlin/com/madness/collision/unit/api_viewing/ui/list/AppListState.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import androidx.activity.result.contract.ActivityResultContracts
2626
import androidx.compose.runtime.Composable
2727
import androidx.compose.runtime.LaunchedEffect
2828
import androidx.compose.runtime.Stable
29-
import androidx.compose.runtime.derivedStateOf
3029
import androidx.compose.runtime.getValue
3130
import androidx.compose.runtime.mutableIntStateOf
3231
import androidx.compose.runtime.mutableStateOf
@@ -116,13 +115,9 @@ class ListHeaderStateImpl(
116115
private val viewModel: AppListViewModel,
117116
private val context: Context,
118117
) : ListHeaderState {
119-
private var scrollY by mutableIntStateOf(0)
120-
override var headerHeight: Int by mutableIntStateOf(0)
121-
override val headerOffsetY: Int by derivedStateOf { -(scrollY.coerceIn(0, headerHeight)) }
122118
override var statsSize: Int by mutableIntStateOf(0)
123119

124120
override fun setTerminalCat(cat: ListSrcCat) = viewModel.setListSrcCat(cat)
125-
override fun updateOffsetY(scrollY: Int) { this.scrollY = scrollY }
126121
override fun showSystemModules() = context.showPage<SystemModulesFragment>()
127122
override fun showStats(options: AppListOptions) {
128123
getStatsFragment(options)?.let(context::showPage)

0 commit comments

Comments
 (0)