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.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue 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.draw.clip
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -164,6 +170,30 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
contract = ActivityResultContracts.StartActivityForResult() contract = ActivityResultContracts.StartActivityForResult()
) { viewModel.fetchModuleList() } ) { 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( Scaffold(
topBar = { topBar = {
SearchAppBar( SearchAppBar(
@@ -289,46 +319,58 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
}, },
floatingActionButton = { floatingActionButton = {
if (!hideInstallButton) { if (!hideInstallButton) {
val moduleInstall = stringResource(id = R.string.module_install) AnimatedVisibility(
val selectZipLauncher = rememberLauncherForActivityResult( visible = showFab,
contract = ActivityResultContracts.StartActivityForResult() enter = scaleIn(
) { result -> animationSpec = tween(200),
if (result.resultCode != RESULT_OK) { initialScale = 0.8f
return@rememberLauncherForActivityResult ) + fadeIn(animationSpec = tween(400)),
} exit = scaleOut(
val data = result.data ?: return@rememberLauncherForActivityResult animationSpec = tween(200),
val clipData = data.clipData targetScale = 0.8f
) + fadeOut(animationSpec = tween(400))
val uris = mutableListOf<Uri>() ) {
if (clipData != null) { val moduleInstall = stringResource(id = R.string.module_install)
for (i in 0 until clipData.itemCount) { val selectZipLauncher = rememberLauncherForActivityResult(
clipData.getItemAt(i)?.uri?.let { uris.add(it) } contract = ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode != RESULT_OK) {
return@rememberLauncherForActivityResult
} }
} else { val data = result.data ?: return@rememberLauncherForActivityResult
data.data?.let { uris.add(it) } 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 ExtendedFloatingActionButton(
onClick = {
viewModel.updateZipUris(uris) // Select the zip files to install
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
navigator.navigate(FlashScreenDestination(FlashIt.FlashModules(uris))) type = "application/zip"
viewModel.clearZipUris() putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
viewModel.markNeedRefresh() }
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), contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
@@ -370,7 +412,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
} }
}, },
context = context, context = context,
snackBarHost = snackBarHost snackBarHost = snackBarHost,
listState = listState
) )
} }
} }
@@ -388,6 +431,7 @@ private fun ModuleList(
onClickModule: (id: String, name: String, hasWebUi: Boolean) -> Unit, onClickModule: (id: String, name: String, hasWebUi: Boolean) -> Unit,
context: Context, context: Context,
snackBarHost: SnackbarHostState, snackBarHost: SnackbarHostState,
listState: LazyListState
) { ) {
val failedEnable = stringResource(R.string.module_failed_to_enable) val failedEnable = stringResource(R.string.module_failed_to_enable)
val failedDisable = stringResource(R.string.module_failed_to_disable) val failedDisable = stringResource(R.string.module_failed_to_disable)
@@ -560,6 +604,7 @@ private fun ModuleList(
isRefreshing = viewModel.isRefreshing isRefreshing = viewModel.isRefreshing
) { ) {
LazyColumn( LazyColumn(
state = listState,
modifier = modifier, modifier = modifier,
verticalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = remember { contentPadding = remember {
@@ -567,7 +612,7 @@ private fun ModuleList(
start = 16.dp, start = 16.dp,
top = 16.dp, top = 16.dp,
end = 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
) )
}, },
) { ) {