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 android.util.Log
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.animation.*
|
||||||
|
import androidx.compose.animation.core.*
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
@@ -38,6 +42,7 @@ import java.io.FileInputStream
|
|||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.net.*
|
import java.net.*
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
import com.sukisu.ultra.ui.theme.CardConfig.cardElevation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,6 +62,9 @@ fun KpmScreen(
|
|||||||
val snackBarHost = remember { SnackbarHostState() }
|
val snackBarHost = remember { SnackbarHostState() }
|
||||||
val confirmDialog = rememberConfirmDialog()
|
val confirmDialog = rememberConfirmDialog()
|
||||||
|
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
val fabVisible by rememberFabVisibilityState(listState)
|
||||||
|
|
||||||
val moduleConfirmContentMap = viewModel.moduleList.associate { module ->
|
val moduleConfirmContentMap = viewModel.moduleList.associate { module ->
|
||||||
val moduleFileName = module.id
|
val moduleFileName = module.id
|
||||||
module.id to stringResource(R.string.confirm_uninstall_content, moduleFileName)
|
module.id to stringResource(R.string.confirm_uninstall_content, moduleFileName)
|
||||||
@@ -283,28 +291,29 @@ fun KpmScreen(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
ExtendedFloatingActionButton(
|
AnimatedFab(visible = fabVisible) {
|
||||||
onClick = {
|
FloatingActionButton(
|
||||||
selectPatchLauncher.launch(
|
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
Intent(Intent.ACTION_GET_CONTENT).apply {
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
type = "application/octet-stream"
|
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),
|
content = {
|
||||||
)
|
Icon(
|
||||||
},
|
painter = painterResource(id = R.drawable.package_import),
|
||||||
text = {
|
contentDescription = null
|
||||||
Text(
|
)
|
||||||
text = stringResource(R.string.kpm_install),
|
}
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
expanded = true,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
|
contentWindowInsets = WindowInsets.safeDrawing.only(
|
||||||
|
WindowInsetsSides.Top + WindowInsetsSides.Horizontal
|
||||||
|
),
|
||||||
snackbarHost = { SnackbarHost(snackBarHost) }
|
snackbarHost = { SnackbarHost(snackBarHost) }
|
||||||
) { padding ->
|
) { padding ->
|
||||||
Column(modifier = Modifier.padding(padding)) {
|
Column(modifier = Modifier.padding(padding)) {
|
||||||
@@ -378,6 +387,7 @@ fun KpmScreen(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 16.dp),
|
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.sukisu.ultra.ui.screen
|
package com.sukisu.ultra.ui.screen
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity.*
|
import android.app.Activity.*
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -15,10 +16,12 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||||||
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
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.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.selection.toggleable
|
import androidx.compose.foundation.selection.toggleable
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
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.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.*
|
import androidx.compose.ui.platform.*
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
@@ -81,6 +85,8 @@ import com.dergoogler.mmrl.platform.Platform
|
|||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.dergoogler.mmrl.platform.model.ModuleConfig
|
import com.dergoogler.mmrl.platform.model.ModuleConfig
|
||||||
import com.dergoogler.mmrl.platform.model.ModuleConfig.Companion.asModuleConfig
|
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
|
import com.sukisu.ultra.ui.theme.getCardElevation
|
||||||
|
|
||||||
// 菜单项数据类
|
// 菜单项数据类
|
||||||
@@ -94,6 +100,7 @@ data class ModuleBottomSheetMenuItem(
|
|||||||
* @author ShirkNeko
|
* @author ShirkNeko
|
||||||
* @date 2025/5/31.
|
* @date 2025/5/31.
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("ResourceType", "AutoboxingStateCreation")
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Destination<RootGraph>
|
@Destination<RootGraph>
|
||||||
@Composable
|
@Composable
|
||||||
@@ -110,6 +117,8 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
skipPartiallyExpanded = true
|
skipPartiallyExpanded = true
|
||||||
)
|
)
|
||||||
var showBottomSheet by remember { mutableStateOf(false) }
|
var showBottomSheet by remember { mutableStateOf(false) }
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
val fabVisible by rememberFabVisibilityState(listState)
|
||||||
|
|
||||||
val selectZipLauncher = rememberLauncherForActivityResult(
|
val selectZipLauncher = rememberLauncherForActivityResult(
|
||||||
contract = ActivityResultContracts.StartActivityForResult()
|
contract = ActivityResultContracts.StartActivityForResult()
|
||||||
@@ -272,9 +281,10 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
if (!hideInstallButton) {
|
AnimatedFab(visible = !hideInstallButton && fabVisible) {
|
||||||
val moduleInstall = stringResource(id = R.string.module_install)
|
FloatingActionButton(
|
||||||
ExtendedFloatingActionButton(
|
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
onClick = {
|
onClick = {
|
||||||
selectZipLauncher.launch(
|
selectZipLauncher.launch(
|
||||||
Intent(Intent.ACTION_GET_CONTENT).apply {
|
Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||||
@@ -283,18 +293,12 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
icon = {
|
content = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Add,
|
painter = painterResource(id = R.drawable.package_import),
|
||||||
contentDescription = moduleInstall,
|
contentDescription = null
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
text = {
|
|
||||||
Text(
|
|
||||||
text = moduleInstall,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
expanded = true,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -334,6 +338,7 @@ fun ModuleScreen(navigator: DestinationsNavigator) {
|
|||||||
ModuleList(
|
ModuleList(
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
|
listState = listState,
|
||||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
boxModifier = Modifier.padding(innerPadding),
|
boxModifier = Modifier.padding(innerPadding),
|
||||||
onInstallModule = {
|
onInstallModule = {
|
||||||
@@ -605,6 +610,7 @@ private fun ModuleBottomSheetMenuItemView(menuItem: ModuleBottomSheetMenuItem) {
|
|||||||
private fun ModuleList(
|
private fun ModuleList(
|
||||||
navigator: DestinationsNavigator,
|
navigator: DestinationsNavigator,
|
||||||
viewModel: ModuleViewModel,
|
viewModel: ModuleViewModel,
|
||||||
|
listState: LazyListState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
boxModifier: Modifier = Modifier,
|
boxModifier: Modifier = Modifier,
|
||||||
onInstallModule: (Uri) -> Unit,
|
onInstallModule: (Uri) -> Unit,
|
||||||
@@ -765,6 +771,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 {
|
||||||
|
|||||||
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