1 /*
2 * Copyright (C) 2022 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 androidx.constraintlayout.compose
18
19 import androidx.compose.ui.Modifier
20 import androidx.compose.ui.geometry.Offset
21 import androidx.compose.ui.geometry.lerp
22 import androidx.compose.ui.layout.layoutId
23 import androidx.compose.ui.platform.testTag
24 import androidx.compose.ui.test.SemanticsNodeInteraction
25 import androidx.compose.ui.test.TouchInjectionScope
26 import androidx.compose.ui.test.performTouchInput
27 import androidx.compose.ui.util.lerp
28 import kotlin.math.roundToInt
29
30 /**
31 * Applies the [id] to the [layoutId] and [testTag] Modifiers.
32 *
33 * This allows using syntax such as: `rule.onNodeWithTag(id)...`
34 */
layoutTestIdnull35 internal fun Modifier.layoutTestId(id: Any): Modifier = testTag(id.toString()).layoutId(id)
36
37 /**
38 * Helper method that will simulate a swipe on the given [SemanticsNodeInteraction].
39 *
40 * Use [from] and [to] to calculate the starting and ending position. [TouchInjectionScope] includes
41 * the dimension of the layout: [TouchInjectionScope.left], [TouchInjectionScope.center], etc.
42 *
43 * If [endWithUp] is false, the touch pointer will remain down at [to]. In which case you'll have to
44 * make sure you lift the pointer later on, eg:
45 * ```
46 * rule.onNodeWithTag("MyTag")
47 * .performTouchInput {
48 * up()
49 * }
50 * ```
51 */
52 internal fun SemanticsNodeInteraction.performSwipe(
53 from: TouchInjectionScope.() -> Offset,
54 to: TouchInjectionScope.() -> Offset,
55 endWithUp: Boolean = true
56 ) {
57 performTouchInput {
58 // Do a periodic swipe between two points that lasts 500ms
59 val start = from()
60 val end = to()
61 val durationMillis = 500L
62 val durationMillisFloat = durationMillis.toFloat()
63
64 // Start touch input
65 down(0, start)
66
67 val steps = (durationMillisFloat / eventPeriodMillis.toFloat()).roundToInt()
68 var step = 0
69
70 val getPositionAt: (Long) -> Offset = { lerp(start, end, it.toFloat() / durationMillis) }
71
72 var tP = 0L
73 while (step++ < steps) {
74 val progress = step / steps.toFloat()
75 val tn = lerp(0, durationMillis, progress)
76 updatePointerTo(0, getPositionAt(tn))
77 move(tn - tP)
78 tP = tn
79 }
80 if (endWithUp) {
81 up()
82 }
83 }
84 }
85