Skip to content

Instantly share code, notes, and snippets.

@NikolaDespotoski
Created June 28, 2024 14:03
Show Gist options
  • Save NikolaDespotoski/e12f15ed28064bc1b491f41298cba5b4 to your computer and use it in GitHub Desktop.
Save NikolaDespotoski/e12f15ed28064bc1b491f41298cba5b4 to your computer and use it in GitHub Desktop.
@file:OptIn(ExperimentalComposeUiApi::class)
import androidx.annotation.DrawableRes
import androidx.annotation.IdRes
import androidx.annotation.RawRes
import androidx.annotation.StringRes
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.SemanticsModifierNode
import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.testTag
import androidx.compose.ui.semantics.testTagsAsResourceId
fun Modifier.testId(tag: String, useAsResourceId: Boolean = true) =
this then SimpleStringTagModifier(tag, useAsResourceId)
fun Modifier.disableInteraction(disabled: Boolean = true) =
this then if (disabled) {
pointerInput(Unit) {
awaitPointerEventScope {
// we should wait for all new pointer events
while (true) {
awaitPointerEvent(pass = PointerEventPass.Initial)
.changes
.forEach(PointerInputChange::consume)
}
}
}
} else {
this
}
private data class SimpleStringTagModifier(
private val tag: String, private val useAsResourceId: Boolean = true
) : ModifierNodeElement<StringTagNode>() {
override fun create() = StringTagNode(tag = tag, useAsResourceId = useAsResourceId)
override fun update(node: StringTagNode) {
node.tag = tag
node.useAsResourceId = useAsResourceId
}
override fun InspectorInfo.inspectableProperties() {
debugInspectorInfo {
name = "id-semantics-simple-string-tag"
properties["tag"] = tag
properties["useAsResourceId"] = useAsResourceId
}
}
}
private data class ResourceIdModifierNode(
@StringRes @IdRes @RawRes @DrawableRes private val id: Int,
private val useAsResourceId: Boolean = true,
) : ModifierNodeElement<ResourceIdNode>() {
override fun create(): ResourceIdNode =
ResourceIdNode(id = id, useAsResourceId = useAsResourceId)
override fun update(node: ResourceIdNode) {
node.id = id
node.useAsResourceId = useAsResourceId
}
override fun InspectorInfo.inspectableProperties() {
debugInspectorInfo {
name = "id-semantics"
properties["id"] = id
properties["useAsResourceId"] = useAsResourceId
}
}
}
private abstract class AbstractTestTagNode(private val useAsResourceId: Boolean) : SemanticsModifierNode, Modifier.Node(){
override fun SemanticsPropertyReceiver.applySemantics() {
testTag = createTestTag()
testTagsAsResourceId = useAsResourceId
}
abstract fun createTestTag(): String
}
private class StringTagNode(
var tag: String,
var useAsResourceId: Boolean = true
) : AbstractTestTagNode(useAsResourceId) {
override fun createTestTag(): String = tag
}
private class ResourceIdNode(
var id: Int,
var useAsResourceId: Boolean = true
) : AbstractTestTagNode(useAsResourceId), CompositionLocalConsumerModifierNode {
override fun createTestTag(): String {
val tag = currentValueOf(LocalContext).resources.getResourceEntryName(id)
return tag
}
}
fun Modifier.testId(
@StringRes @IdRes @RawRes @DrawableRes id: Int,
useAsResourceId: Boolean = true
) = this then ResourceIdModifierNode(id, useAsResourceId)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment