From 24b443ea801746c21d13af1f04f245af3638e1f8 Mon Sep 17 00:00:00 2001 From: howie Date: Sun, 20 Oct 2024 18:37:40 +0800 Subject: [PATCH 1/2] update: [Indication]->[MiuixIndication] Optimize details (with [SuperDropdown]) update: [MiuixPopupUtil] Reduce the slide out speed of dialog. --- .../yukonga/miuix/kmp/extra/SuperDropdown.kt | 24 +-- .../top/yukonga/miuix/kmp/utils/Indication.kt | 122 ------------- .../miuix/kmp/utils/MiuixIndication.kt | 161 ++++++++++++++++++ .../yukonga/miuix/kmp/utils/MiuixPopupUtil.kt | 2 +- 4 files changed, 174 insertions(+), 135 deletions(-) delete mode 100644 miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/Indication.kt create mode 100644 miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixIndication.kt diff --git a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperDropdown.kt b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperDropdown.kt index dca25aba..9798a704 100644 --- a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperDropdown.kt +++ b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperDropdown.kt @@ -71,7 +71,7 @@ import top.yukonga.miuix.kmp.theme.MiuixTheme import top.yukonga.miuix.kmp.utils.BackHandler import top.yukonga.miuix.kmp.utils.MiuixPopupUtil.Companion.dismissPopup import top.yukonga.miuix.kmp.utils.MiuixPopupUtil.Companion.showPopup -import top.yukonga.miuix.kmp.utils.PopupInteraction +import top.yukonga.miuix.kmp.utils.HoldDownInteraction import top.yukonga.miuix.kmp.utils.SmoothRoundedCornerShape import top.yukonga.miuix.kmp.utils.getWindowSize import kotlin.math.roundToInt @@ -114,6 +114,7 @@ fun SuperDropdown( val interactionSource = remember { MutableInteractionSource() } val isDropdownExpanded = remember { mutableStateOf(false) } val coroutineScope = rememberCoroutineScope() + val held = remember { mutableStateOf(null) } val hapticFeedback = LocalHapticFeedback.current val actionColor = if (enabled) MiuixTheme.colorScheme.onSurfaceVariantActions else MiuixTheme.colorScheme.disabledOnSecondaryVariant var alignLeft by rememberSaveable { mutableStateOf(true) } @@ -125,9 +126,15 @@ fun SuperDropdown( DisposableEffect(Unit) { onDispose { dismissPopup(isDropdownExpanded) + } + } + + if (!isDropdownExpanded.value) { + held.value?.let { oldValue -> coroutineScope.launch { - interactionSource.emit(PopupInteraction.Close) + interactionSource.emit(HoldDownInteraction.Release(oldValue)) } + held.value = null } } @@ -181,7 +188,9 @@ fun SuperDropdown( isDropdownExpanded.value = enabled hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) coroutineScope.launch { - interactionSource.emit(PopupInteraction.Open) + interactionSource.emit(HoldDownInteraction.Hold().also { + held.value = it + }) } } }, @@ -227,9 +236,6 @@ fun SuperDropdown( BackHandler(enabled = isDropdownExpanded.value) { dismissPopup(isDropdownExpanded) - coroutineScope.launch { - interactionSource.emit(PopupInteraction.Close) - } } showPopup( @@ -246,9 +252,6 @@ fun SuperDropdown( detectTapGestures( onTap = { dismissPopup(isDropdownExpanded) - coroutineScope.launch { - interactionSource.emit(PopupInteraction.Close) - } } ) } @@ -293,9 +296,6 @@ fun SuperDropdown( onSelectedIndexChange = { hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) onSelectedIndexChange(it) - coroutineScope.launch { - interactionSource.emit(PopupInteraction.Close) - } dismissPopup(isDropdownExpanded) }, textWidthDp = textWidthDp, diff --git a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/Indication.kt b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/Indication.kt deleted file mode 100644 index 5e39dc6c..00000000 --- a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/Indication.kt +++ /dev/null @@ -1,122 +0,0 @@ -package top.yukonga.miuix.kmp.utils - -import androidx.compose.animation.core.Animatable -import androidx.compose.foundation.Indication -import androidx.compose.foundation.IndicationNodeFactory -import androidx.compose.foundation.interaction.FocusInteraction -import androidx.compose.foundation.interaction.HoverInteraction -import androidx.compose.foundation.interaction.Interaction -import androidx.compose.foundation.interaction.InteractionSource -import androidx.compose.foundation.interaction.PressInteraction -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.drawscope.ContentDrawScope -import androidx.compose.ui.node.DelegatableNode -import androidx.compose.ui.node.DrawModifierNode -import androidx.compose.ui.node.invalidateDraw -import kotlinx.coroutines.launch -import kotlinx.datetime.Clock - -/** - * Miuix default [Indication] that draws a rectangular overlay when pressed. - */ -class MiuixIndication( - private val backgroundColor: Color = Color.Black -) : IndicationNodeFactory { - override fun create(interactionSource: InteractionSource): DelegatableNode = - DefaultIndicationInstance(interactionSource, backgroundColor) - - override fun hashCode(): Int = -1 - - override fun equals(other: Any?) = other === this - - private class DefaultIndicationInstance( - private val interactionSource: InteractionSource, - private val backgroundColor: Color - ) : Modifier.Node(), DrawModifierNode { - private var isPressed = false - private var isHovered = false - private var isFocused = false - private var isPopupExpanded = false - private var lastClickTime = 0L - private val clickThreshold = 300L - private val animatedAlpha = Animatable(0f) - - override fun onAttach() { - coroutineScope.launch { - interactionSource.interactions.collect { interaction -> - when (interaction) { - is PopupInteraction.Open -> { - isPopupExpanded = true - animatedAlpha.animateTo(0.12f) - } - - is PopupInteraction.Close -> { - isPopupExpanded = false - if (!isHovered && !isFocused && !isPressed) { - animatedAlpha.animateTo(0f) - } - } - - is PressInteraction.Press -> { - isPressed = true - animatedAlpha.animateTo(0.12f) - } - - is PressInteraction.Cancel, is PressInteraction.Release -> { - isPressed = false - val currentTime = Clock.System.now().toEpochMilliseconds() - if (currentTime - lastClickTime < clickThreshold) { - animatedAlpha.snapTo(0f) - } - lastClickTime = currentTime - if (!isHovered && !isFocused && !isPopupExpanded) { - animatedAlpha.animateTo(0f) - } else if (isPopupExpanded) { - animatedAlpha.animateTo(0.12f) - } else if (isHovered || isFocused) { - animatedAlpha.animateTo(0.06f) - } - } - - is HoverInteraction.Enter -> { - isHovered = true - animatedAlpha.animateTo(0.06f) - } - - is HoverInteraction.Exit -> { - isHovered = false - if (!isFocused && !isPressed && !isPopupExpanded) { - animatedAlpha.animateTo(0f) - } - } - - is FocusInteraction.Focus -> { - isFocused = true - animatedAlpha.animateTo(0.06f) - } - - is FocusInteraction.Unfocus -> { - isFocused = false - if (!isHovered && !isPressed && !isPopupExpanded) { - animatedAlpha.animateTo(0f) - } - } - } - invalidateDraw() - } - } - } - - override fun ContentDrawScope.draw() { - drawContent() - drawRect(color = backgroundColor.copy(alpha = animatedAlpha.value), size = size) - } - } -} - - -open class PopupInteraction : Interaction { - object Open : PopupInteraction() - object Close : PopupInteraction() -} diff --git a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixIndication.kt b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixIndication.kt new file mode 100644 index 00000000..acbdd777 --- /dev/null +++ b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixIndication.kt @@ -0,0 +1,161 @@ +package top.yukonga.miuix.kmp.utils + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.tween +import androidx.compose.foundation.Indication +import androidx.compose.foundation.IndicationNodeFactory +import androidx.compose.foundation.interaction.FocusInteraction +import androidx.compose.foundation.interaction.HoverInteraction +import androidx.compose.foundation.interaction.Interaction +import androidx.compose.foundation.interaction.InteractionSource +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.PressInteraction +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.node.DelegatableNode +import androidx.compose.ui.node.DrawModifierNode +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +/** + * Miuix default [Indication] that draws a rectangular overlay when pressed. + */ +class MiuixIndication( + private val backgroundColor: Color = Color.Black +) : IndicationNodeFactory { + override fun create(interactionSource: InteractionSource): DelegatableNode = + MiuixIndicationInstance(interactionSource, backgroundColor) + + override fun hashCode(): Int = -1 + + override fun equals(other: Any?) = other === this + + private class MiuixIndicationInstance( + private val interactionSource: InteractionSource, + private val backgroundColor: Color + ) : Modifier.Node(), DrawModifierNode { + private var isPressed = false + private var isHovered = false + private var isFocused = false + private val animatedAlpha = Animatable(0f) + private var pressedAnimation: Job? = null + private var restingAnimation: Job? = null + + private suspend fun updateStates() { + animatedAlpha.stop() + var targetAlpha = 0.0f + if (isHovered) targetAlpha += 0.06f + if (isFocused) targetAlpha += 0.08f + if (isPressed) targetAlpha += 0.1f + if (targetAlpha == 0.0f) { + restingAnimation = coroutineScope.launch { + pressedAnimation?.join() + animatedAlpha.animateTo(0f, tween(150)) + } + } else { + restingAnimation?.cancel() + pressedAnimation?.cancel() + pressedAnimation = coroutineScope.launch { + animatedAlpha.animateTo(targetAlpha, tween(150)) + } + } + } + + override fun onAttach() { + coroutineScope.launch { + var pressed = false + var hovered = false + var focused = false + var held = false + interactionSource.interactions.collect { interaction -> + when (interaction) { + is PressInteraction.Press -> pressed = true + is PressInteraction.Release, is PressInteraction.Cancel -> pressed = false + is HoverInteraction.Enter -> hovered = true + is HoverInteraction.Exit -> hovered = false + is FocusInteraction.Focus -> focused = true + is FocusInteraction.Unfocus -> focused = false + is HoldDownInteraction.Hold -> held = true + is HoldDownInteraction.Release -> held = false + else -> return@collect + } + var invalidateNeeded = false + if (isPressed != (pressed || held)) { + isPressed = (pressed || held) + invalidateNeeded = true + } + if (isHovered != hovered) { + isHovered = hovered + invalidateNeeded = true + } + if (isFocused != focused) { + isFocused = focused + invalidateNeeded = true + } + if (invalidateNeeded) { + updateStates() + } + } + } + } + + override fun ContentDrawScope.draw() { + // Draw content + drawContent() + // Draw foreground + drawRect(color = backgroundColor.copy(alpha = animatedAlpha.value), size = size) + } + } +} + +/** + * An interaction related to hold down events. + * + * @see Hold + * @see Release + */ +interface HoldDownInteraction : Interaction { + /** + * An interaction representing a hold down event on a component. + * + * @see Release + */ + class Hold : HoldDownInteraction + + /** + * An interaction representing a [Hold] event being released on a component. + * + * @property hold the source [Hold] interaction that is being released + * + * @see Hold + */ + class Release(val hold: Hold) : HoldDownInteraction +} + +/** + * Subscribes to this [MutableInteractionSource] and returns a [State] representing whether this + * component is selected or not. + * + * @return [State] representing whether this component is being focused or not + */ +@Composable +fun InteractionSource.collectIsHeldDownAsState(): State { + val isHeldDown = remember { mutableStateOf(false) } + LaunchedEffect(this) { + val holdInteraction = mutableListOf() + interactions.collect { interaction -> + when (interaction) { + is HoldDownInteraction.Hold -> holdInteraction.add(interaction) + is HoldDownInteraction.Release -> holdInteraction.remove(interaction.hold) + } + isHeldDown.value = holdInteraction.isNotEmpty() + } + } + return isHeldDown +} \ No newline at end of file diff --git a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixPopupUtil.kt b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixPopupUtil.kt index 975eba2c..03e41f6a 100644 --- a/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixPopupUtil.kt +++ b/miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixPopupUtil.kt @@ -125,7 +125,7 @@ class MiuixPopupUtil { } else { slideInVertically( initialOffsetY = { fullHeight -> fullHeight }, - animationSpec = spring(0.92f, 500f) + animationSpec = spring(0.92f, 400f) ) }, exit = if (largeScreen.invoke().value) { From 7a865130aa6c44f62075f410e6af69bb1d901f05 Mon Sep 17 00:00:00 2001 From: YuKongA <70465933+YuKongA@users.noreply.github.com> Date: Sun, 20 Oct 2024 18:45:58 +0800 Subject: [PATCH 2/2] Remove datetime library --- gradle/libs.versions.toml | 2 -- miuix/build.gradle.kts | 1 - 2 files changed, 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index db84566d..5874ba3e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,13 +9,11 @@ androidx-window = "1.3.0" compose-plugin = "1.7.0" dokka = "1.9.20" kotlin = "2.0.21" -kotlinx-datetime = "0.6.0" [libraries] androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" } androidx-window = { group = "androidx.window", name = "window", version.ref = "androidx-window" } jetbrains-compose-window-size = { module = "org.jetbrains.compose.material3:material3-window-size-class", version.ref = "compose-plugin" } -jetbrains-kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } diff --git a/miuix/build.gradle.kts b/miuix/build.gradle.kts index cd3573d8..1a09c2b5 100644 --- a/miuix/build.gradle.kts +++ b/miuix/build.gradle.kts @@ -47,7 +47,6 @@ kotlin { implementation(compose.ui) implementation(compose.components.resources) implementation(libs.jetbrains.compose.window.size) - implementation(libs.jetbrains.kotlinx.datetime) } } }