1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package platform.test.motion.compose.values
18 
19 import androidx.compose.runtime.Stable
20 import androidx.compose.ui.Modifier
21 import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
22 import androidx.compose.ui.node.ModifierNodeElement
23 import androidx.compose.ui.node.SemanticsModifierNode
24 import androidx.compose.ui.node.currentValueOf
25 import androidx.compose.ui.platform.InspectorInfo
26 import androidx.compose.ui.semantics.SemanticsPropertyKey
27 import androidx.compose.ui.semantics.SemanticsPropertyReceiver
28 
29 /**
30  * A key for a value exported to motion tests.
31  *
32  * No two instances of the key are identical, no matter the [name]. Code exporting values using this
33  * method must also provide access to this instance for the test.
34  */
35 class MotionTestValueKey<T>(name: String) {
36     val semanticsPropertyKey = SemanticsPropertyKey<T>(name)
37 
equalsnull38     override fun equals(other: Any?): Boolean {
39         if (this === other) return true
40         if (other !is MotionTestValueKey<*>) return false
41 
42         return semanticsPropertyKey == other.semanticsPropertyKey
43     }
44 
hashCodenull45     override fun hashCode(): Int {
46         return semanticsPropertyKey.hashCode()
47     }
48 }
49 
50 interface MotionTestValueScope {
exportAsnull51     infix fun <T> T.exportAs(key: MotionTestValueKey<T>)
52 }
53 
54 /**
55  * Exports a value for motion tests.
56  *
57  * [values] is not read except during the test, thus does not trigger additional recompositions.
58  */
59 @Stable
60 fun Modifier.motionTestValues(values: MotionTestValueScope.() -> Unit) =
61     this then MotionTestValuesElement(values)
62 
63 private data class MotionTestValuesElement(val values: MotionTestValueScope.() -> Unit) :
64     ModifierNodeElement<MotionTestValuesNode>() {
65 
66     override fun create(): MotionTestValuesNode {
67         return MotionTestValuesNode(values)
68     }
69 
70     override fun update(node: MotionTestValuesNode) {
71         node.values = values
72     }
73 
74     override fun InspectorInfo.inspectableProperties() {
75         name = "motionTestValues"
76     }
77 }
78 
79 private class MotionTestValuesNode(var values: MotionTestValueScope.() -> Unit) :
80     Modifier.Node(), SemanticsModifierNode, CompositionLocalConsumerModifierNode {
81 
applySemanticsnull82     override fun SemanticsPropertyReceiver.applySemantics() {
83         if (currentValueOf(LocalEnableMotionTestValueCollection)) {
84             values.invoke(
85                 object : MotionTestValueScope {
86                     override fun <T> T.exportAs(key: MotionTestValueKey<T>) {
87                         this@applySemantics[key.semanticsPropertyKey] = this
88                     }
89                 }
90             )
91         }
92     }
93 }
94