manager: change FAB visibility with scrolling gestures on module screen list

This commit is contained in:
Rifat Azad
2025-06-28 19:45:32 +06:00
parent d80a3ebcda
commit 93dc61e113

View File

@@ -77,6 +77,12 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.snapshotFlow
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.core.tween
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
@@ -164,6 +170,30 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
contract = ActivityResultContracts.StartActivityForResult()
) { viewModel.fetchModuleList() }
val listState = rememberLazyListState()
var showFab by remember { mutableStateOf(true) }
LaunchedEffect(listState) {
var lastIndex = listState.firstVisibleItemIndex
var lastOffset = listState.firstVisibleItemScrollOffset
snapshotFlow { listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset }
.collect { (currIndex, currOffset) ->
val isScrollingDown = currIndex > lastIndex ||
(currIndex == lastIndex && currOffset > lastOffset + 4)
val isScrollingUp = currIndex < lastIndex ||
(currIndex == lastIndex && currOffset < lastOffset - 4)
when {
isScrollingDown && showFab -> showFab = false
isScrollingUp && !showFab -> showFab = true
}
lastIndex = currIndex
lastOffset = currOffset
}
}
Scaffold(
topBar = {
SearchAppBar(
@@ -289,46 +319,58 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
},
floatingActionButton = {
if (!hideInstallButton) {
val moduleInstall = stringResource(id = R.string.module_install)
val selectZipLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode != RESULT_OK) {
return@rememberLauncherForActivityResult
}
val data = result.data ?: return@rememberLauncherForActivityResult
val clipData = data.clipData
val uris = mutableListOf<Uri>()
if (clipData != null) {
for (i in 0 until clipData.itemCount) {
clipData.getItemAt(i)?.uri?.let { uris.add(it) }
AnimatedVisibility(
visible = showFab,
enter = scaleIn(
animationSpec = tween(200),
initialScale = 0.8f
) + fadeIn(animationSpec = tween(400)),
exit = scaleOut(
animationSpec = tween(200),
targetScale = 0.8f
) + fadeOut(animationSpec = tween(400))
) {
val moduleInstall = stringResource(id = R.string.module_install)
val selectZipLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode != RESULT_OK) {
return@rememberLauncherForActivityResult
}
} else {
data.data?.let { uris.add(it) }
val data = result.data ?: return@rememberLauncherForActivityResult
val clipData = data.clipData
val uris = mutableListOf<Uri>()
if (clipData != null) {
for (i in 0 until clipData.itemCount) {
clipData.getItemAt(i)?.uri?.let { uris.add(it) }
}
} else {
data.data?.let { uris.add(it) }
}
if (uris.isEmpty()) return@rememberLauncherForActivityResult
viewModel.updateZipUris(uris)
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(uris)))
viewModel.clearZipUris()
viewModel.markNeedRefresh()
}
if (uris.isEmpty()) return@rememberLauncherForActivityResult
viewModel.updateZipUris(uris)
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(uris)))
viewModel.clearZipUris()
viewModel.markNeedRefresh()
ExtendedFloatingActionButton(
onClick = {
// Select the zip files to install
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/zip"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}
selectZipLauncher.launch(intent)
},
icon = { Icon(Icons.Filled.Add, moduleInstall) },
text = { Text(text = moduleInstall) },
)
}
ExtendedFloatingActionButton(
onClick = {
// Select the zip files to install
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
type = "application/zip"
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}
selectZipLauncher.launch(intent)
},
icon = { Icon(Icons.Filled.Add, moduleInstall) },
text = { Text(text = moduleInstall) },
)
}
},
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
@@ -370,7 +412,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
}
},
context = context,
snackBarHost = snackBarHost
snackBarHost = snackBarHost,
listState = listState
)
}
}
@@ -388,6 +431,7 @@ private fun ModuleList(
onClickModule: (id: String, name: String, hasWebUi: Boolean) -> Unit,
context: Context,
snackBarHost: SnackbarHostState,
listState: LazyListState
) {
val failedEnable = stringResource(R.string.module_failed_to_enable)
val failedDisable = stringResource(R.string.module_failed_to_disable)
@@ -560,6 +604,7 @@ private fun ModuleList(
isRefreshing = viewModel.isRefreshing
) {
LazyColumn(
state = listState,
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = remember {
@@ -567,7 +612,7 @@ private fun ModuleList(
start = 16.dp,
top = 16.dp,
end = 16.dp,
bottom = 16.dp + 56.dp + 16.dp + 48.dp + 6.dp /* Scaffold Fab Spacing + Fab container height + SnackBar height */
bottom = 16.dp
)
},
) {