diff --git a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/StorageTree.kt b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/StorageTree.kt index b8f475a..e523bda 100644 --- a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/StorageTree.kt +++ b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/StorageTree.kt @@ -38,6 +38,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.github.nullptroma.wallenc.domain.datatypes.Tree import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo import com.github.nullptroma.wallenc.presentation.R +import com.github.nullptroma.wallenc.presentation.elements.indication.ScaleIndication +import com.github.nullptroma.wallenc.presentation.extensions.clickableDebounced @Composable fun StorageTree( @@ -54,9 +56,8 @@ fun StorageTree( modifier = Modifier .fillMaxWidth() .clip(cardShape) - .clickable { + .clickableDebounced(debounceMs = 500) { onClick(tree) - //viewModel.printStorageInfoToLog(cur) }, shape = cardShape, elevation = CardDefaults.cardElevation( @@ -134,7 +135,10 @@ fun StorageTree( showRemoveConfirmationDiaglog = false onRemove(tree) }, - title = stringResource(R.string.remove_confirmation_dialog, metaInfo.name ?: "") + title = stringResource( + R.string.remove_confirmation_dialog, + metaInfo.name ?: "" + ) ) } } diff --git a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/indication/ScaleIndication.kt b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/indication/ScaleIndication.kt new file mode 100644 index 0000000..131ddfc --- /dev/null +++ b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/indication/ScaleIndication.kt @@ -0,0 +1,61 @@ +package com.github.nullptroma.wallenc.presentation.elements.indication + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.spring +import androidx.compose.foundation.IndicationNodeFactory +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.interaction.PressInteraction +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.scale +import androidx.compose.ui.node.DelegatableNode +import androidx.compose.ui.node.DrawModifierNode +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +private class ScaleNode(private val interactionSource: InteractionSource) : + Modifier.Node(), DrawModifierNode { + + var currentPressPosition: Offset = Offset.Zero + val animatedScalePercent = Animatable(1f) + + private suspend fun animateToPressed(pressPosition: Offset) { + currentPressPosition = pressPosition + animatedScalePercent.animateTo(0.9f, spring()) + } + + private suspend fun animateToResting() { + animatedScalePercent.animateTo(1f, spring()) + } + + override fun onAttach() { + coroutineScope.launch { + interactionSource.interactions.collectLatest { interaction -> + when (interaction) { + is PressInteraction.Press -> animateToPressed(interaction.pressPosition) + is PressInteraction.Release -> animateToResting() + is PressInteraction.Cancel -> animateToResting() + } + } + } + } + + override fun ContentDrawScope.draw() { + scale( + scale = animatedScalePercent.value, + pivot = currentPressPosition + ) { + this@draw.drawContent() + } + } +} + +object ScaleIndication : IndicationNodeFactory { + override fun create(interactionSource: InteractionSource): DelegatableNode { + return ScaleNode(interactionSource) + } + + override fun equals(other: Any?): Boolean = other === ScaleIndication + override fun hashCode() = 100 +} \ No newline at end of file diff --git a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/extensions/Modifier.kt b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/extensions/Modifier.kt index b6b1c1e..f3dbe1c 100644 --- a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/extensions/Modifier.kt +++ b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/extensions/Modifier.kt @@ -1,7 +1,16 @@ package com.github.nullptroma.wallenc.presentation.extensions +import androidx.compose.foundation.Indication +import androidx.compose.foundation.IndicationNodeFactory +import androidx.compose.foundation.LocalIndication +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.composed import androidx.compose.ui.layout.layout +import androidx.compose.ui.platform.debugInspectorInfo +import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.Dp fun Modifier.ignoreHorizontalParentPadding(horizontal: Dp): Modifier { @@ -22,4 +31,44 @@ fun Modifier.ignoreVerticalParentPadding(vertical: Dp): Modifier { placeable.place(0, 0) } } +} + +fun Modifier.clickableDebounced( + interactionSource: MutableInteractionSource? = null, + indication: Indication? = null, + enabled: Boolean = true, + onClickLabel: String? = null, + role: Role? = null, + debounceMs: Long = 300, + onClick: () -> Unit +): Modifier = composed( + inspectorInfo = debugInspectorInfo { + name = "clickableDebounced" + properties["enabled"] = enabled + properties["onClickLabel"] = onClickLabel + properties["role"] = role + properties["onClick"] = onClick + } +) { + var latest: Long = 0 + val ind = indication ?: LocalIndication.current + val interSource = if (ind is IndicationNodeFactory) { + null + } else { + interactionSource ?: remember { MutableInteractionSource() } + } + return@composed this@clickableDebounced.clickable( + interactionSource = interSource, + indication = ind, + enabled = enabled, + onClickLabel = onClickLabel, + role = role, + onClick = { + val now = System.currentTimeMillis() + if (now - latest >= debounceMs) { + latest = now + onClick() + } + } + ) } \ No newline at end of file diff --git a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreen.kt b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreen.kt index 17311c6..cc1ca12 100644 --- a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreen.kt +++ b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreen.kt @@ -1,54 +1,22 @@ package com.github.nullptroma.wallenc.presentation.screens.main.screens.local.vault -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.gestures.detectTapGestures -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.CardElevation import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon -import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.PlatformTextStyle -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontStyle -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.github.nullptroma.wallenc.domain.datatypes.Tree -import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo -import com.github.nullptroma.wallenc.presentation.R import com.github.nullptroma.wallenc.presentation.elements.StorageTree -import kotlin.random.Random @OptIn(ExperimentalMaterial3Api::class) @Composable