Skip to content

Commit 8d824c5

Browse files
authored
library: Optimize MiuixIndication (#27)
* update: [Indication]->[MiuixIndication] Optimize details (with [SuperDropdown]) * update: [MiuixPopupUtil] Reduce the slide out speed of dialog.
1 parent a85b179 commit 8d824c5

File tree

6 files changed

+174
-138
lines changed

6 files changed

+174
-138
lines changed

gradle/libs.versions.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,11 @@ androidx-window = "1.3.0"
99
compose-plugin = "1.7.0"
1010
dokka = "1.9.20"
1111
kotlin = "2.0.21"
12-
kotlinx-datetime = "0.6.0"
1312

1413
[libraries]
1514
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" }
1615
androidx-window = { group = "androidx.window", name = "window", version.ref = "androidx-window" }
1716
jetbrains-compose-window-size = { module = "org.jetbrains.compose.material3:material3-window-size-class", version.ref = "compose-plugin" }
18-
jetbrains-kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
1917

2018
[plugins]
2119
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }

miuix/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ kotlin {
4747
implementation(compose.ui)
4848
implementation(compose.components.resources)
4949
implementation(libs.jetbrains.compose.window.size)
50-
implementation(libs.jetbrains.kotlinx.datetime)
5150
}
5251
}
5352
}

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/extra/SuperDropdown.kt

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ import top.yukonga.miuix.kmp.theme.MiuixTheme
7171
import top.yukonga.miuix.kmp.utils.BackHandler
7272
import top.yukonga.miuix.kmp.utils.MiuixPopupUtil.Companion.dismissPopup
7373
import top.yukonga.miuix.kmp.utils.MiuixPopupUtil.Companion.showPopup
74-
import top.yukonga.miuix.kmp.utils.PopupInteraction
74+
import top.yukonga.miuix.kmp.utils.HoldDownInteraction
7575
import top.yukonga.miuix.kmp.utils.SmoothRoundedCornerShape
7676
import top.yukonga.miuix.kmp.utils.getWindowSize
7777
import kotlin.math.roundToInt
@@ -114,6 +114,7 @@ fun SuperDropdown(
114114
val interactionSource = remember { MutableInteractionSource() }
115115
val isDropdownExpanded = remember { mutableStateOf(false) }
116116
val coroutineScope = rememberCoroutineScope()
117+
val held = remember { mutableStateOf<HoldDownInteraction.Hold?>(null) }
117118
val hapticFeedback = LocalHapticFeedback.current
118119
val actionColor = if (enabled) MiuixTheme.colorScheme.onSurfaceVariantActions else MiuixTheme.colorScheme.disabledOnSecondaryVariant
119120
var alignLeft by rememberSaveable { mutableStateOf(true) }
@@ -125,9 +126,15 @@ fun SuperDropdown(
125126
DisposableEffect(Unit) {
126127
onDispose {
127128
dismissPopup(isDropdownExpanded)
129+
}
130+
}
131+
132+
if (!isDropdownExpanded.value) {
133+
held.value?.let { oldValue ->
128134
coroutineScope.launch {
129-
interactionSource.emit(PopupInteraction.Close)
135+
interactionSource.emit(HoldDownInteraction.Release(oldValue))
130136
}
137+
held.value = null
131138
}
132139
}
133140

@@ -181,7 +188,9 @@ fun SuperDropdown(
181188
isDropdownExpanded.value = enabled
182189
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
183190
coroutineScope.launch {
184-
interactionSource.emit(PopupInteraction.Open)
191+
interactionSource.emit(HoldDownInteraction.Hold().also {
192+
held.value = it
193+
})
185194
}
186195
}
187196
},
@@ -227,9 +236,6 @@ fun SuperDropdown(
227236

228237
BackHandler(enabled = isDropdownExpanded.value) {
229238
dismissPopup(isDropdownExpanded)
230-
coroutineScope.launch {
231-
interactionSource.emit(PopupInteraction.Close)
232-
}
233239
}
234240

235241
showPopup(
@@ -246,9 +252,6 @@ fun SuperDropdown(
246252
detectTapGestures(
247253
onTap = {
248254
dismissPopup(isDropdownExpanded)
249-
coroutineScope.launch {
250-
interactionSource.emit(PopupInteraction.Close)
251-
}
252255
}
253256
)
254257
}
@@ -293,9 +296,6 @@ fun SuperDropdown(
293296
onSelectedIndexChange = {
294297
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
295298
onSelectedIndexChange(it)
296-
coroutineScope.launch {
297-
interactionSource.emit(PopupInteraction.Close)
298-
}
299299
dismissPopup(isDropdownExpanded)
300300
},
301301
textWidthDp = textWidthDp,

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/Indication.kt

Lines changed: 0 additions & 122 deletions
This file was deleted.
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package top.yukonga.miuix.kmp.utils
2+
3+
import androidx.compose.animation.core.Animatable
4+
import androidx.compose.animation.core.tween
5+
import androidx.compose.foundation.Indication
6+
import androidx.compose.foundation.IndicationNodeFactory
7+
import androidx.compose.foundation.interaction.FocusInteraction
8+
import androidx.compose.foundation.interaction.HoverInteraction
9+
import androidx.compose.foundation.interaction.Interaction
10+
import androidx.compose.foundation.interaction.InteractionSource
11+
import androidx.compose.foundation.interaction.MutableInteractionSource
12+
import androidx.compose.foundation.interaction.PressInteraction
13+
import androidx.compose.runtime.Composable
14+
import androidx.compose.runtime.LaunchedEffect
15+
import androidx.compose.runtime.State
16+
import androidx.compose.runtime.mutableStateOf
17+
import androidx.compose.runtime.remember
18+
import androidx.compose.ui.Modifier
19+
import androidx.compose.ui.graphics.Color
20+
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
21+
import androidx.compose.ui.node.DelegatableNode
22+
import androidx.compose.ui.node.DrawModifierNode
23+
import kotlinx.coroutines.Job
24+
import kotlinx.coroutines.launch
25+
26+
/**
27+
* Miuix default [Indication] that draws a rectangular overlay when pressed.
28+
*/
29+
class MiuixIndication(
30+
private val backgroundColor: Color = Color.Black
31+
) : IndicationNodeFactory {
32+
override fun create(interactionSource: InteractionSource): DelegatableNode =
33+
MiuixIndicationInstance(interactionSource, backgroundColor)
34+
35+
override fun hashCode(): Int = -1
36+
37+
override fun equals(other: Any?) = other === this
38+
39+
private class MiuixIndicationInstance(
40+
private val interactionSource: InteractionSource,
41+
private val backgroundColor: Color
42+
) : Modifier.Node(), DrawModifierNode {
43+
private var isPressed = false
44+
private var isHovered = false
45+
private var isFocused = false
46+
private val animatedAlpha = Animatable(0f)
47+
private var pressedAnimation: Job? = null
48+
private var restingAnimation: Job? = null
49+
50+
private suspend fun updateStates() {
51+
animatedAlpha.stop()
52+
var targetAlpha = 0.0f
53+
if (isHovered) targetAlpha += 0.06f
54+
if (isFocused) targetAlpha += 0.08f
55+
if (isPressed) targetAlpha += 0.1f
56+
if (targetAlpha == 0.0f) {
57+
restingAnimation = coroutineScope.launch {
58+
pressedAnimation?.join()
59+
animatedAlpha.animateTo(0f, tween(150))
60+
}
61+
} else {
62+
restingAnimation?.cancel()
63+
pressedAnimation?.cancel()
64+
pressedAnimation = coroutineScope.launch {
65+
animatedAlpha.animateTo(targetAlpha, tween(150))
66+
}
67+
}
68+
}
69+
70+
override fun onAttach() {
71+
coroutineScope.launch {
72+
var pressed = false
73+
var hovered = false
74+
var focused = false
75+
var held = false
76+
interactionSource.interactions.collect { interaction ->
77+
when (interaction) {
78+
is PressInteraction.Press -> pressed = true
79+
is PressInteraction.Release, is PressInteraction.Cancel -> pressed = false
80+
is HoverInteraction.Enter -> hovered = true
81+
is HoverInteraction.Exit -> hovered = false
82+
is FocusInteraction.Focus -> focused = true
83+
is FocusInteraction.Unfocus -> focused = false
84+
is HoldDownInteraction.Hold -> held = true
85+
is HoldDownInteraction.Release -> held = false
86+
else -> return@collect
87+
}
88+
var invalidateNeeded = false
89+
if (isPressed != (pressed || held)) {
90+
isPressed = (pressed || held)
91+
invalidateNeeded = true
92+
}
93+
if (isHovered != hovered) {
94+
isHovered = hovered
95+
invalidateNeeded = true
96+
}
97+
if (isFocused != focused) {
98+
isFocused = focused
99+
invalidateNeeded = true
100+
}
101+
if (invalidateNeeded) {
102+
updateStates()
103+
}
104+
}
105+
}
106+
}
107+
108+
override fun ContentDrawScope.draw() {
109+
// Draw content
110+
drawContent()
111+
// Draw foreground
112+
drawRect(color = backgroundColor.copy(alpha = animatedAlpha.value), size = size)
113+
}
114+
}
115+
}
116+
117+
/**
118+
* An interaction related to hold down events.
119+
*
120+
* @see Hold
121+
* @see Release
122+
*/
123+
interface HoldDownInteraction : Interaction {
124+
/**
125+
* An interaction representing a hold down event on a component.
126+
*
127+
* @see Release
128+
*/
129+
class Hold : HoldDownInteraction
130+
131+
/**
132+
* An interaction representing a [Hold] event being released on a component.
133+
*
134+
* @property hold the source [Hold] interaction that is being released
135+
*
136+
* @see Hold
137+
*/
138+
class Release(val hold: Hold) : HoldDownInteraction
139+
}
140+
141+
/**
142+
* Subscribes to this [MutableInteractionSource] and returns a [State] representing whether this
143+
* component is selected or not.
144+
*
145+
* @return [State] representing whether this component is being focused or not
146+
*/
147+
@Composable
148+
fun InteractionSource.collectIsHeldDownAsState(): State<Boolean> {
149+
val isHeldDown = remember { mutableStateOf(false) }
150+
LaunchedEffect(this) {
151+
val holdInteraction = mutableListOf<HoldDownInteraction.Hold>()
152+
interactions.collect { interaction ->
153+
when (interaction) {
154+
is HoldDownInteraction.Hold -> holdInteraction.add(interaction)
155+
is HoldDownInteraction.Release -> holdInteraction.remove(interaction.hold)
156+
}
157+
isHeldDown.value = holdInteraction.isNotEmpty()
158+
}
159+
}
160+
return isHeldDown
161+
}

miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/MiuixPopupUtil.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ class MiuixPopupUtil {
125125
} else {
126126
slideInVertically(
127127
initialOffsetY = { fullHeight -> fullHeight },
128-
animationSpec = spring(0.92f, 500f)
128+
animationSpec = spring(0.92f, 400f)
129129
)
130130
},
131131
exit = if (largeScreen.invoke().value) {

0 commit comments

Comments
 (0)