manager: Add visibility state management for floating action buttons
- Get change button style from Apatch
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
package com.sukisu.ultra.ui.component
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.*
|
||||
|
||||
@SuppressLint("AutoboxingStateCreation")
|
||||
@Composable
|
||||
fun rememberFabVisibilityState(listState: LazyListState): State<Boolean> {
|
||||
var previousScrollOffset by remember { mutableStateOf(0) }
|
||||
var previousIndex by remember { mutableStateOf(0) }
|
||||
val fabVisible = remember { mutableStateOf(true) }
|
||||
|
||||
LaunchedEffect(listState) {
|
||||
snapshotFlow { listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset }
|
||||
.collect { (index, offset) ->
|
||||
if (previousIndex == 0 && previousScrollOffset == 0) {
|
||||
fabVisible.value = true
|
||||
} else {
|
||||
val isScrollingDown = when {
|
||||
index > previousIndex -> false
|
||||
index < previousIndex -> true
|
||||
else -> offset < previousScrollOffset
|
||||
}
|
||||
|
||||
fabVisible.value = isScrollingDown
|
||||
}
|
||||
|
||||
previousIndex = index
|
||||
previousScrollOffset = offset
|
||||
}
|
||||
}
|
||||
|
||||
return fabVisible
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AnimatedFab(
|
||||
visible: Boolean,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = visible,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut()
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,13 @@ import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
@@ -38,6 +42,7 @@ import java.io.FileInputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.net.*
|
||||
import android.app.Activity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
||||
|
||||
/**
|
||||
@@ -57,6 +62,9 @@ fun KpmScreen(
|
||||
val snackBarHost = remember { SnackbarHostState() }
|
||||
val confirmDialog = rememberConfirmDialog()
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
val fabVisible by rememberFabVisibilityState(listState)
|
||||
|
||||
val moduleConfirmContentMap = viewModel.moduleList.associate { module ->
|
||||
val moduleFileName = module.id
|
||||
module.id to stringResource(R.string.confirm_uninstall_content, moduleFileName)
|
||||
@@ -283,7 +291,10 @@ fun KpmScreen(
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
ExtendedFloatingActionButton(
|
||||
AnimatedFab(visible = fabVisible) {
|
||||
FloatingActionButton(
|
||||
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
onClick = {
|
||||
selectPatchLauncher.launch(
|
||||
Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
@@ -291,20 +302,18 @@ fun KpmScreen(
|
||||
}
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
content = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Add,
|
||||
contentDescription = stringResource(R.string.kpm_install),
|
||||
painter = painterResource(id = R.drawable.package_import),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(R.string.kpm_install),
|
||||
)
|
||||
},
|
||||
expanded = true,
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
contentWindowInsets = WindowInsets.safeDrawing.only(
|
||||
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
|
||||
),
|
||||
snackbarHost = { SnackbarHost(snackBarHost) }
|
||||
) { padding ->
|
||||
Column(modifier = Modifier.padding(padding)) {
|
||||
@@ -378,6 +387,7 @@ fun KpmScreen(
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.sukisu.ultra.ui.screen
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity.*
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -15,10 +16,12 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.selection.toggleable
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
@@ -36,6 +39,7 @@ import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.*
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -81,6 +85,8 @@ import com.dergoogler.mmrl.platform.Platform
|
||||
import androidx.core.net.toUri
|
||||
import com.dergoogler.mmrl.platform.model.ModuleConfig
|
||||
import com.dergoogler.mmrl.platform.model.ModuleConfig.Companion.asModuleConfig
|
||||
import com.sukisu.ultra.ui.component.AnimatedFab
|
||||
import com.sukisu.ultra.ui.component.rememberFabVisibilityState
|
||||
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||
|
||||
// 菜单项数据类
|
||||
@@ -94,6 +100,7 @@ data class ModuleBottomSheetMenuItem(
|
||||
* @author ShirkNeko
|
||||
* @date 2025/5/31.
|
||||
*/
|
||||
@SuppressLint("ResourceType", "AutoboxingStateCreation")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Destination<RootGraph>
|
||||
@Composable
|
||||
@@ -110,6 +117,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
skipPartiallyExpanded = true
|
||||
)
|
||||
var showBottomSheet by remember { mutableStateOf(false) }
|
||||
val listState = rememberLazyListState()
|
||||
val fabVisible by rememberFabVisibilityState(listState)
|
||||
|
||||
val selectZipLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult()
|
||||
@@ -272,9 +281,10 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
if (!hideInstallButton) {
|
||||
val moduleInstall = stringResource(id = R.string.module_install)
|
||||
ExtendedFloatingActionButton(
|
||||
AnimatedFab(visible = !hideInstallButton && fabVisible) {
|
||||
FloatingActionButton(
|
||||
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
onClick = {
|
||||
selectZipLauncher.launch(
|
||||
Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||
@@ -283,18 +293,12 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
}
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
content = {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Add,
|
||||
contentDescription = moduleInstall,
|
||||
painter = painterResource(id = R.drawable.package_import),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
text = moduleInstall,
|
||||
)
|
||||
},
|
||||
expanded = true,
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -334,6 +338,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
||||
ModuleList(
|
||||
navigator = navigator,
|
||||
viewModel = viewModel,
|
||||
listState = listState,
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
boxModifier = Modifier.padding(innerPadding),
|
||||
onInstallModule = {
|
||||
@@ -605,6 +610,7 @@ private fun ModuleBottomSheetMenuItemView(menuItem: ModuleBottomSheetMenuItem) {
|
||||
private fun ModuleList(
|
||||
navigator: DestinationsNavigator,
|
||||
viewModel: ModuleViewModel,
|
||||
listState: LazyListState,
|
||||
modifier: Modifier = Modifier,
|
||||
boxModifier: Modifier = Modifier,
|
||||
onInstallModule: (Uri) -> Unit,
|
||||
@@ -765,6 +771,7 @@ private fun ModuleList(
|
||||
isRefreshing = viewModel.isRefreshing
|
||||
) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
contentPadding = remember {
|
||||
|
||||
42
manager/app/src/main/res/drawable/package_import.xml
Normal file
42
manager/app/src/main/res/drawable/package_import.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M12,21l-8,-4.5v-9l8,-4.5l8,4.5v4.5"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M12,12l8,-4.5"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M12,12v9"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M12,12l-8,-4.5"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M22,18h-7"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffff"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M18,15l-3,3l3,3"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#ffff"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
Reference in New Issue
Block a user