Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
1 change: 0 additions & 1 deletion miuix/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ kotlin {
implementation(compose.ui)
implementation(compose.components.resources)
implementation(libs.jetbrains.compose.window.size)
implementation(libs.jetbrains.kotlinx.datetime)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -114,6 +114,7 @@ fun SuperDropdown(
val interactionSource = remember { MutableInteractionSource() }
val isDropdownExpanded = remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
val held = remember { mutableStateOf<HoldDownInteraction.Hold?>(null) }
val hapticFeedback = LocalHapticFeedback.current
val actionColor = if (enabled) MiuixTheme.colorScheme.onSurfaceVariantActions else MiuixTheme.colorScheme.disabledOnSecondaryVariant
var alignLeft by rememberSaveable { mutableStateOf(true) }
Expand All @@ -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
}
}

Expand Down Expand Up @@ -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
})
}
}
},
Expand Down Expand Up @@ -227,9 +236,6 @@ fun SuperDropdown(

BackHandler(enabled = isDropdownExpanded.value) {
dismissPopup(isDropdownExpanded)
coroutineScope.launch {
interactionSource.emit(PopupInteraction.Close)
}
}

showPopup(
Expand All @@ -246,9 +252,6 @@ fun SuperDropdown(
detectTapGestures(
onTap = {
dismissPopup(isDropdownExpanded)
coroutineScope.launch {
interactionSource.emit(PopupInteraction.Close)
}
}
)
}
Expand Down Expand Up @@ -293,9 +296,6 @@ fun SuperDropdown(
onSelectedIndexChange = {
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
onSelectedIndexChange(it)
coroutineScope.launch {
interactionSource.emit(PopupInteraction.Close)
}
dismissPopup(isDropdownExpanded)
},
textWidthDp = textWidthDp,
Expand Down
122 changes: 0 additions & 122 deletions miuix/src/commonMain/kotlin/top/yukonga/miuix/kmp/utils/Indication.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<Boolean> {
val isHeldDown = remember { mutableStateOf(false) }
LaunchedEffect(this) {
val holdInteraction = mutableListOf<HoldDownInteraction.Hold>()
interactions.collect { interaction ->
when (interaction) {
is HoldDownInteraction.Hold -> holdInteraction.add(interaction)
is HoldDownInteraction.Release -> holdInteraction.remove(interaction.hold)
}
isHeldDown.value = holdInteraction.isNotEmpty()
}
}
return isHeldDown
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down