@@ -20,6 +20,10 @@ import androidx.compose.foundation.layout.height
2020import androidx.compose.foundation.layout.padding
2121import androidx.compose.foundation.lazy.LazyColumn
2222import androidx.compose.foundation.lazy.rememberLazyListState
23+ import androidx.compose.material.ExperimentalMaterialApi
24+ import androidx.compose.material.pullrefresh.PullRefreshIndicator
25+ import androidx.compose.material.pullrefresh.pullRefresh
26+ import androidx.compose.material.pullrefresh.rememberPullRefreshState
2327import androidx.compose.material3.ExperimentalMaterial3Api
2428import androidx.compose.material3.Icon
2529import androidx.compose.material3.MaterialTheme
@@ -31,6 +35,7 @@ import androidx.compose.runtime.derivedStateOf
3135import androidx.compose.runtime.getValue
3236import androidx.compose.runtime.mutableStateOf
3337import androidx.compose.runtime.remember
38+ import androidx.compose.runtime.rememberCoroutineScope
3439import androidx.compose.runtime.setValue
3540import androidx.compose.runtime.snapshotFlow
3641import androidx.compose.ui.Alignment
@@ -57,13 +62,16 @@ import com.androidvip.sysctlgui.ui.main.MainViewModel
5762import com.androidvip.sysctlgui.ui.main.MainViewState
5863import com.androidvip.sysctlgui.ui.params.DocumentationBottomSheet
5964import com.androidvip.sysctlgui.utils.browse
65+ import kotlinx.coroutines.delay
6066import kotlinx.coroutines.flow.distinctUntilChanged
6167import kotlinx.coroutines.flow.filter
6268import kotlinx.coroutines.flow.map
69+ import kotlinx.coroutines.launch
6370import org.koin.compose.viewmodel.koinViewModel
6471import java.io.File
72+ import kotlin.time.Duration.Companion.seconds
6573
66- @OptIn(ExperimentalMaterial3Api ::class )
74+ @OptIn(ExperimentalMaterialApi :: class , ExperimentalMaterial3Api ::class )
6775@Composable
6876fun ParamBrowseScreen (
6977 mainViewModel : MainViewModel = koinViewModel(),
@@ -115,7 +123,9 @@ fun ParamBrowseScreen(
115123 backEnabled = state.backEnabled,
116124 onBackPressed = {
117125 viewModel.onEvent(ParamBrowseViewEvent .BackRequested )
118- }
126+ },
127+ isRefreshing = state.loading,
128+ onRefresh = { viewModel.onEvent(ParamBrowseViewEvent .RefreshRequested ) }
119129 )
120130
121131 documentation?.let {
@@ -126,6 +136,8 @@ fun ParamBrowseScreen(
126136 }
127137}
128138
139+
140+ @OptIn(ExperimentalMaterialApi ::class )
129141@Composable
130142private fun ParamBrowseScreenContent (
131143 params : List <UiKernelParam >,
@@ -135,8 +147,11 @@ private fun ParamBrowseScreenContent(
135147 onDocumentationClicked : (ParamDocumentation ) -> Unit ,
136148 backEnabled : Boolean = false,
137149 onBackPressed : () -> Unit ,
150+ isRefreshing : Boolean ,
151+ onRefresh : () -> Unit
138152) {
139153 val listState = rememberLazyListState()
154+ val pullRefreshState = rememberPullRefreshState(refreshing = isRefreshing, onRefresh = onRefresh)
140155 var headerVisible by remember { mutableStateOf(backEnabled) }
141156
142157 BackHandler (enabled = backEnabled, onBack = onBackPressed)
@@ -173,7 +188,11 @@ private fun ParamBrowseScreenContent(
173188
174189 val finalHeaderVisible = (headerVisible || isAtTop) && backEnabled
175190
176- Box (modifier = Modifier .fillMaxSize()) {
191+ Box (
192+ modifier = Modifier
193+ .fillMaxSize()
194+ .pullRefresh(pullRefreshState),
195+ ) {
177196 val spacerHeight by animateDpAsState(if (finalHeaderVisible) 56 .dp else 0 .dp)
178197 LazyColumn (
179198 state = listState,
@@ -226,6 +245,14 @@ private fun ParamBrowseScreenContent(
226245 onClicked = { onDocumentationClicked(documentation!! ) }
227246 )
228247 }
248+
249+ PullRefreshIndicator (
250+ refreshing = isRefreshing,
251+ state = pullRefreshState,
252+ modifier = Modifier .align(Alignment .TopCenter ),
253+ backgroundColor = MaterialTheme .colorScheme.surfaceContainerHighest,
254+ contentColor = MaterialTheme .colorScheme.tertiary
255+ )
229256 }
230257}
231258
@@ -287,6 +314,8 @@ internal fun ParamBrowseScreenContentPreview() {
287314 var params by remember(currentPath) {
288315 mutableStateOf(mapFilesToParams(File (currentPath).listFiles()))
289316 }
317+ var isRefreshingPreview by remember { mutableStateOf(false ) }
318+ val scope = rememberCoroutineScope()
290319
291320 SysctlGuiTheme (dynamicColor = true ) {
292321 Box (modifier = Modifier .background(MaterialTheme .colorScheme.background)) {
@@ -307,6 +336,14 @@ internal fun ParamBrowseScreenContentPreview() {
307336 onDocumentationClicked = {},
308337 backEnabled = currentPath != root.path,
309338 onBackPressed = { currentPath = File (currentPath).parent ? : root.path },
339+ isRefreshing = isRefreshingPreview,
340+ onRefresh = {
341+ scope.launch {
342+ isRefreshingPreview = true
343+ delay(2 .seconds)
344+ isRefreshingPreview = false
345+ }
346+ }
310347 )
311348 }
312349 }
0 commit comments