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.foundation.content
18
19 import androidx.compose.foundation.ExperimentalFoundationApi
20 import androidx.compose.foundation.content.internal.DynamicReceiveContentConfiguration
21 import androidx.compose.foundation.content.internal.ModifierLocalReceiveContent
22 import androidx.compose.foundation.content.internal.ReceiveContentConfiguration
23 import androidx.compose.foundation.content.internal.ReceiveContentDragAndDropNode
24 import androidx.compose.foundation.content.internal.dragAndDropRequestPermission
25 import androidx.compose.ui.Modifier
26 import androidx.compose.ui.modifier.ModifierLocalMap
27 import androidx.compose.ui.modifier.ModifierLocalModifierNode
28 import androidx.compose.ui.modifier.modifierLocalMapOf
29 import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
30 import androidx.compose.ui.node.DelegatingNode
31 import androidx.compose.ui.node.ModifierNodeElement
32 import androidx.compose.ui.platform.InspectorInfo
33
34 /**
35 * Configures the current node and any children nodes as a Content Receiver.
36 *
37 * Content in this context refers to a [TransferableContent] that could be received from another app
38 * through Drag-and-Drop, Copy/Paste, or from the Software Keyboard.
39 *
40 * There is no pre-filtering for the received content by media type, e.g. software Keyboard would
41 * assume that the app can handle any content that's sent to it. Therefore, it's crucial to check
42 * the received content's type and other related information before reading and processing it.
43 * Please refer to [TransferableContent.hasMediaType] and [TransferableContent.clipMetadata] to
44 * learn more about how to do proper checks on the received item.
45 *
46 * Note that only [androidx.compose.foundation.text.input.TextFieldState] override of the text field
47 * supports being a content receiver.
48 *
49 * @param receiveContentListener Listener to respond to the receive event. This interface also
50 * includes a set of callbacks for certain Drag-and-Drop state changes. Please checkout
51 * [ReceiveContentListener] docs for an explanation of each callback.
52 * @sample androidx.compose.foundation.samples.ReceiveContentFullSample
53 * @see TransferableContent
54 * @see hasMediaType
55 */
56 @Suppress("ExecutorRegistration")
57 @ExperimentalFoundationApi
contentReceivernull58 fun Modifier.contentReceiver(receiveContentListener: ReceiveContentListener): Modifier =
59 then(ReceiveContentElement(receiveContentListener = receiveContentListener))
60
61 @OptIn(ExperimentalFoundationApi::class)
62 internal data class ReceiveContentElement(val receiveContentListener: ReceiveContentListener) :
63 ModifierNodeElement<ReceiveContentNode>() {
64 override fun create(): ReceiveContentNode {
65 return ReceiveContentNode(receiveContentListener)
66 }
67
68 override fun update(node: ReceiveContentNode) {
69 node.updateNode(receiveContentListener)
70 }
71
72 override fun InspectorInfo.inspectableProperties() {
73 name = "receiveContent"
74 }
75 }
76
77 // This node uses ModifierLocals instead of TraversableNode to find ancestor due to b/311181532.
78 // Since the usage of modifier locals are minimal and exactly correspond to how we would use
79 // TraversableNode if it was available, the switch should be fairly easy when the bug is fixed.
80 @OptIn(ExperimentalFoundationApi::class)
81 internal class ReceiveContentNode(var receiveContentListener: ReceiveContentListener) :
82 DelegatingNode(), ModifierLocalModifierNode, CompositionLocalConsumerModifierNode {
83
84 private val receiveContentConfiguration: ReceiveContentConfiguration =
85 DynamicReceiveContentConfiguration(this)
86
87 // The default provided configuration is the one supplied to this node. Once the node is
88 // attached, it should provide a delegating version to ancestor nodes.
89 override val providedValues: ModifierLocalMap =
90 modifierLocalMapOf(ModifierLocalReceiveContent to receiveContentConfiguration)
91
92 init {
93 delegate(
94 ReceiveContentDragAndDropNode(
95 receiveContentConfiguration = receiveContentConfiguration,
<lambda>null96 dragAndDropRequestPermission = { dragAndDropRequestPermission(it) }
97 )
98 )
99 }
100
updateNodenull101 fun updateNode(receiveContentListener: ReceiveContentListener) {
102 this.receiveContentListener = receiveContentListener
103 }
104 }
105