1 /*
2 * Copyright 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 androidx.compose.material3.adaptive.layout
18
19 import androidx.compose.foundation.clickable
20 import androidx.compose.foundation.interaction.MutableInteractionSource
21 import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
22 import androidx.compose.runtime.Composable
23 import androidx.compose.runtime.rememberCoroutineScope
24 import androidx.compose.ui.Modifier
25 import androidx.compose.ui.node.ModifierNodeElement
26 import androidx.compose.ui.node.ParentDataModifierNode
27 import androidx.compose.ui.platform.InspectorInfo
28 import androidx.compose.ui.platform.debugInspectorInfo
29 import androidx.compose.ui.semantics.SemanticsActions
30 import androidx.compose.ui.semantics.SemanticsConfiguration
31 import androidx.compose.ui.semantics.SemanticsPropertyReceiver
32 import androidx.compose.ui.semantics.contentDescription
33 import androidx.compose.ui.semantics.getOrNull
34 import androidx.compose.ui.semantics.onClick
35 import androidx.compose.ui.semantics.semantics
36 import androidx.compose.ui.semantics.stateDescription
37 import androidx.compose.ui.unit.Density
38 import androidx.compose.ui.unit.Dp
39 import kotlinx.coroutines.launch
40
41 /**
42 * This function sets up the default semantics of pane expansion drag handles with the given
43 * [PaneExpansionState]. It will provide suitable [contentDescription] as well as [onClick] function
44 * to move the pane expansion among anchors that can be operated via accessibility services.
45 *
46 * It's supposed to be used with a [PaneScaffoldScope.paneExpansionDraggable] modifier, or a plain
47 * [semantics] modifier associated with a drag handle composable.
48 */
49 @ExperimentalMaterial3AdaptiveApi
50 @Composable
PaneExpansionStatenull51 fun PaneExpansionState.defaultDragHandleSemantics(): SemanticsPropertyReceiver.() -> Unit {
52 val coroutineScope = rememberCoroutineScope()
53 val contentDesc = getString(Strings.defaultPaneExpansionDragHandleContentDescription)
54 val currentAnchor = currentAnchor
55 val stateDesc =
56 if (currentAnchor != null) {
57 getString(
58 Strings.defaultPaneExpansionDragHandleStateDescription,
59 currentAnchor.description
60 )
61 } else {
62 null
63 }
64 val nextAnchor = nextAnchor
65 val actionLabel =
66 if (nextAnchor != null) {
67 getString(
68 Strings.defaultPaneExpansionDragHandleActionDescription,
69 nextAnchor.description
70 )
71 } else {
72 null
73 }
74 return semantics@{
75 contentDescription = contentDesc
76 if (stateDesc != null) {
77 stateDescription = stateDesc
78 }
79 if (nextAnchor == null) {
80 // TODO(conrachen): handle this case
81 return@semantics
82 }
83 onClick(label = actionLabel) {
84 coroutineScope.launch { animateTo(nextAnchor) }
85 return@onClick true
86 }
87 }
88 }
89
systemGestureExclusionnull90 internal expect fun Modifier.systemGestureExclusion(): Modifier
91
92 internal data class MinTouchTargetSizeElement(val size: Dp) :
93 ModifierNodeElement<MinTouchTargetSizeNode>() {
94 private val inspectorInfo = debugInspectorInfo {
95 name = "minTouchTargetSize"
96 properties["size"] = size
97 }
98
99 override fun create(): MinTouchTargetSizeNode {
100 return MinTouchTargetSizeNode(size)
101 }
102
103 override fun update(node: MinTouchTargetSizeNode) {
104 node.size = size
105 }
106
107 override fun InspectorInfo.inspectableProperties() {
108 inspectorInfo()
109 }
110 }
111
112 internal class MinTouchTargetSizeNode(var size: Dp) : ParentDataModifierNode, Modifier.Node() {
modifyParentDatanull113 override fun Density.modifyParentData(parentData: Any?) =
114 ((parentData as? PaneScaffoldParentDataImpl) ?: PaneScaffoldParentDataImpl()).also {
115 it.minTouchTargetSize = size
116 }
117 }
118
semanticsActionnull119 internal fun Modifier.semanticsAction(
120 semanticsProperties: (SemanticsPropertyReceiver.() -> Unit),
121 interactionSource: MutableInteractionSource
122 ): Modifier {
123 val semanticsConfiguration = SemanticsConfiguration().also { it.semanticsProperties() }
124 return this.then(
125 semanticsConfiguration.getOrNull(SemanticsActions.OnClick)?.action?.let {
126 Modifier.clickable(interactionSource = interactionSource, indication = null) { it() }
127 } ?: Modifier
128 )
129 }
130