Skip to content

Commit aafc5ed

Browse files
committed
feat: protect status bar when scrolling
1 parent 1bb1912 commit aafc5ed

File tree

4 files changed

+77
-10
lines changed

4 files changed

+77
-10
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package day.vitayuzu.neodb.ui.component
2+
3+
import androidx.compose.animation.AnimatedVisibility
4+
import androidx.compose.foundation.Canvas
5+
import androidx.compose.foundation.gestures.ScrollableState
6+
import androidx.compose.foundation.layout.WindowInsets
7+
import androidx.compose.foundation.layout.fillMaxSize
8+
import androidx.compose.foundation.layout.statusBars
9+
import androidx.compose.material3.MaterialTheme
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.geometry.Size
13+
import androidx.compose.ui.graphics.Brush
14+
import androidx.compose.ui.graphics.Color
15+
import androidx.compose.ui.platform.LocalDensity
16+
17+
/**
18+
* Add translucent background under status bar when scrolling.
19+
* See: https://developer.android.com/develop/ui/compose/system/system-bars
20+
*/
21+
@Suppress("ktlint:compose:modifier-missing-check")
22+
@Composable
23+
fun StatusBarProtection(
24+
scrollState: ScrollableState,
25+
color: Color = MaterialTheme.colorScheme.surfaceContainerLow,
26+
) {
27+
AnimatedVisibility(scrollState.canScrollBackward) {
28+
val height = WindowInsets.statusBars.getTop(LocalDensity.current).toFloat()
29+
Canvas(Modifier.fillMaxSize()) {
30+
val gradient = Brush.verticalGradient(
31+
colors = listOf(
32+
color.copy(alpha = 1f),
33+
color.copy(alpha = .8f),
34+
Color.Transparent,
35+
),
36+
startY = 0f,
37+
endY = height,
38+
)
39+
drawRect(
40+
brush = gradient,
41+
size = Size(size.width, height),
42+
)
43+
}
44+
}
45+
}

app/src/main/kotlin/day/vitayuzu/neodb/ui/page/detail/DetailPage.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.statusBars
2525
import androidx.compose.foundation.layout.width
2626
import androidx.compose.foundation.lazy.LazyColumn
2727
import androidx.compose.foundation.lazy.items
28+
import androidx.compose.foundation.lazy.rememberLazyListState
2829
import androidx.compose.material.icons.Icons
2930
import androidx.compose.material.icons.automirrored.filled.ArrowForward
3031
import androidx.compose.material.icons.filled.Add
@@ -72,6 +73,7 @@ import day.vitayuzu.neodb.ui.component.SharedEntryCardKey
7273
import day.vitayuzu.neodb.ui.component.SharedFab
7374
import day.vitayuzu.neodb.ui.component.SharedFabKey
7475
import day.vitayuzu.neodb.ui.component.StarsWithScores
76+
import day.vitayuzu.neodb.ui.component.StatusBarProtection
7577
import day.vitayuzu.neodb.ui.component.UserMarkCard
7678
import day.vitayuzu.neodb.ui.model.Detail
7779
import day.vitayuzu.neodb.ui.model.Mark
@@ -212,7 +214,9 @@ private fun DetailContent(
212214
isShowingAll: Boolean = false,
213215
showMore: () -> Unit = {},
214216
) {
217+
val lazyListState = rememberLazyListState()
215218
LazyColumn(
219+
state = lazyListState,
216220
modifier = modifier
217221
.fillMaxSize()
218222
.padding(horizontal = 16.dp)
@@ -284,6 +288,8 @@ private fun DetailContent(
284288
}
285289
}
286290
}
291+
292+
StatusBarProtection(lazyListState)
287293
}
288294

289295
@Composable

app/src/main/kotlin/day/vitayuzu/neodb/ui/page/home/HomeScreen.kt

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
3636
import androidx.lifecycle.compose.collectAsStateWithLifecycle
3737
import coil3.compose.AsyncImage
3838
import day.vitayuzu.neodb.ui.component.SharedSearchFab
39+
import day.vitayuzu.neodb.ui.component.StatusBarProtection
3940
import day.vitayuzu.neodb.ui.model.Entry
4041
import day.vitayuzu.neodb.util.AppNavigator
4142
import day.vitayuzu.neodb.util.EntryType
@@ -50,23 +51,27 @@ fun HomeScreen(
5051
sharedBottomBar: @Composable () -> Unit,
5152
) {
5253
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
53-
LocalNavigator.current
54+
55+
val scrollState = rememberScrollState()
5456

5557
// TODO: show fetched data immediately
5658
Scaffold(
5759
modifier = modifier,
5860
bottomBar = sharedBottomBar,
5961
floatingActionButton = { SharedSearchFab() },
60-
) {
62+
) { padding ->
6163
PullToRefreshBox(
6264
isRefreshing = uiState.isLoading,
6365
onRefresh = { viewModel.updateTrending() },
64-
modifier = Modifier.padding(it).consumeWindowInsets(it),
6566
) {
6667
Column(
6768
// PullToRefresh relies on child scroll event to detect scroll gestures,
6869
// so make sure child composable has enough height.
69-
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
70+
modifier = Modifier
71+
.fillMaxSize()
72+
.verticalScroll(scrollState)
73+
.padding(padding)
74+
.consumeWindowInsets(padding),
7075
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
7176
) {
7277
// Book
@@ -102,6 +107,8 @@ fun HomeScreen(
102107
}
103108
}
104109
}
110+
111+
StatusBarProtection(scrollState)
105112
}
106113

107114
@OptIn(ExperimentalSharedTransitionApi::class)

app/src/main/kotlin/day/vitayuzu/neodb/ui/page/search/SearchPage.kt

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import androidx.compose.animation.fadeOut
44
import androidx.compose.animation.scaleIn
55
import androidx.compose.animation.scaleOut
66
import androidx.compose.foundation.layout.Box
7-
import androidx.compose.foundation.layout.consumeWindowInsets
7+
import androidx.compose.foundation.layout.PaddingValues
88
import androidx.compose.foundation.layout.fillMaxSize
99
import androidx.compose.foundation.layout.fillMaxWidth
1010
import androidx.compose.foundation.layout.padding
1111
import androidx.compose.foundation.layout.size
1212
import androidx.compose.foundation.lazy.LazyColumn
1313
import androidx.compose.foundation.lazy.items
14+
import androidx.compose.foundation.lazy.rememberLazyListState
1415
import androidx.compose.foundation.text.KeyboardOptions
1516
import androidx.compose.foundation.text.input.TextFieldLineLimits
1617
import androidx.compose.foundation.text.input.TextFieldState
@@ -45,6 +46,7 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
4546
import day.vitayuzu.neodb.R
4647
import day.vitayuzu.neodb.ui.component.EntryMarkCard
4748
import day.vitayuzu.neodb.ui.component.EntryTypeFilterChipsRow
49+
import day.vitayuzu.neodb.ui.component.StatusBarProtection
4850
import day.vitayuzu.neodb.ui.model.Entry
4951
import day.vitayuzu.neodb.util.BASE_URL
5052
import day.vitayuzu.neodb.util.EntryType
@@ -64,7 +66,7 @@ fun SearchPage(modifier: Modifier = Modifier, viewModel: SearchViewModel = hiltV
6466
enter = scaleIn(),
6567
exit = scaleOut() + fadeOut(),
6668
),
67-
) {
69+
) { padding ->
6870
val textFieldState = rememberTextFieldState()
6971

7072
// Perform search request
@@ -75,14 +77,15 @@ fun SearchPage(modifier: Modifier = Modifier, viewModel: SearchViewModel = hiltV
7577
.collectLatest { viewModel.onSearch(it.toString()) }
7678
}
7779

78-
Box(Modifier.padding(it).consumeWindowInsets(it).fillMaxSize()) {
80+
Box(Modifier.fillMaxSize()) {
7981
if (viewModel.isSearching) {
8082
LoadingIndicator(
8183
Modifier.align(Alignment.Center).size(128.dp),
8284
)
8385
}
8486
SearchPageContent(
85-
state = textFieldState,
87+
windowInsetsPadding = padding,
88+
textFieldState = textFieldState,
8689
instanceName = viewModel.instanceName,
8790
result = viewModel.searchResult.toList(),
8891
)
@@ -95,19 +98,23 @@ data object SearchPageKey
9598
@Composable
9699
private fun SearchPageContent(
97100
modifier: Modifier = Modifier,
98-
state: TextFieldState = rememberTextFieldState(),
101+
windowInsetsPadding: PaddingValues = PaddingValues(),
102+
textFieldState: TextFieldState = rememberTextFieldState(),
99103
instanceName: String = BASE_URL,
100104
result: List<Entry> = emptyList(),
101105
) {
102106
val selectedEntryTypes = rememberSaveable { mutableStateSetOf<EntryType>() }
107+
val lazyListState = rememberLazyListState()
103108

104109
LazyColumn(
105110
modifier = modifier,
111+
state = lazyListState,
106112
horizontalAlignment = Alignment.CenterHorizontally,
113+
contentPadding = windowInsetsPadding,
107114
) {
108115
item {
109116
SearchBarInputField(
110-
state = state,
117+
state = textFieldState,
111118
instanceName = instanceName,
112119
modifier = Modifier
113120
.fillMaxWidth()
@@ -135,6 +142,8 @@ private fun SearchPageContent(
135142
EntryMarkCard(entry = it, mark = null)
136143
}
137144
}
145+
146+
StatusBarProtection(lazyListState)
138147
}
139148

140149
@Composable

0 commit comments

Comments
 (0)