diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/component/FabVisibilityState.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/component/FabVisibilityState.kt new file mode 100644 index 00000000..888adcc4 --- /dev/null +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/component/FabVisibilityState.kt @@ -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 { + 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() + } +} \ No newline at end of file diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Kpm.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Kpm.kt index 6d51d8ed..d4bd0bd6 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Kpm.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Kpm.kt @@ -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,28 +291,29 @@ fun KpmScreen( ) }, floatingActionButton = { - ExtendedFloatingActionButton( - onClick = { - selectPatchLauncher.launch( - Intent(Intent.ACTION_GET_CONTENT).apply { - type = "application/octet-stream" - } - ) - }, - icon = { - Icon( - imageVector = Icons.Filled.Add, - contentDescription = stringResource(R.string.kpm_install), - ) - }, - text = { - Text( - text = stringResource(R.string.kpm_install), - ) - }, - expanded = true, - ) + AnimatedFab(visible = fabVisible) { + FloatingActionButton( + contentColor = MaterialTheme.colorScheme.onPrimary, + containerColor = MaterialTheme.colorScheme.primary, + onClick = { + selectPatchLauncher.launch( + Intent(Intent.ACTION_GET_CONTENT).apply { + type = "application/octet-stream" + } + ) + }, + content = { + Icon( + painter = painterResource(id = R.drawable.package_import), + contentDescription = null + ) + } + ) + } }, + 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) diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt index f06fa82a..a28c06fa 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Module.kt @@ -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 @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 { diff --git a/manager/app/src/main/res/drawable/package_import.xml b/manager/app/src/main/res/drawable/package_import.xml new file mode 100644 index 00000000..3e8d07d0 --- /dev/null +++ b/manager/app/src/main/res/drawable/package_import.xml @@ -0,0 +1,42 @@ + + + + + + + + \ No newline at end of file