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