You've already forked KernelSU-Next
mirror of
https://github.com/KernelSU-Next/KernelSU-Next.git
synced 2025-08-27 23:46:34 +00:00
manager: change FAB visibility with scrolling gestures on module screen list
This commit is contained in:
@@ -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
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in New Issue
Block a user