1 /*
2 * Copyright 2023 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.text.input.internal
18
19 import androidx.compose.foundation.text.LegacyTextFieldState
20 import androidx.compose.foundation.text.selection.TextFieldSelectionManager
21 import androidx.compose.runtime.getValue
22 import androidx.compose.runtime.mutableStateOf
23 import androidx.compose.runtime.setValue
24 import androidx.compose.ui.Modifier
25 import androidx.compose.ui.layout.LayoutCoordinates
26 import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
27 import androidx.compose.ui.node.GlobalPositionAwareModifierNode
28 import androidx.compose.ui.node.ModifierNodeElement
29 import androidx.compose.ui.node.currentValueOf
30 import androidx.compose.ui.platform.InspectorInfo
31 import androidx.compose.ui.platform.LocalSoftwareKeyboardController
32 import androidx.compose.ui.platform.LocalViewConfiguration
33 import androidx.compose.ui.platform.PlatformTextInputModifierNode
34 import androidx.compose.ui.platform.PlatformTextInputSession
35 import androidx.compose.ui.platform.SoftwareKeyboardController
36 import androidx.compose.ui.platform.ViewConfiguration
37 import androidx.compose.ui.platform.establishTextInputSession
38 import kotlinx.coroutines.CoroutineStart
39 import kotlinx.coroutines.Job
40 import kotlinx.coroutines.launch
41
42 /**
43 * Connects a [LegacyPlatformTextInputServiceAdapter] to the PlatformTextInput system. This modifier
44 * must be applied to the text field in order for the [LegacyPlatformTextInputServiceAdapter] to
45 * function.
46 */
legacyTextInputAdapternull47 internal fun Modifier.legacyTextInputAdapter(
48 serviceAdapter: LegacyPlatformTextInputServiceAdapter,
49 legacyTextFieldState: LegacyTextFieldState,
50 textFieldSelectionManager: TextFieldSelectionManager
51 ): Modifier =
52 this then
53 LegacyAdaptingPlatformTextInputModifier(
54 serviceAdapter,
55 legacyTextFieldState,
56 textFieldSelectionManager
57 )
58
59 private data class LegacyAdaptingPlatformTextInputModifier(
60 val serviceAdapter: LegacyPlatformTextInputServiceAdapter,
61 val legacyTextFieldState: LegacyTextFieldState,
62 val textFieldSelectionManager: TextFieldSelectionManager
63 ) : ModifierNodeElement<LegacyAdaptingPlatformTextInputModifierNode>() {
64
65 override fun create(): LegacyAdaptingPlatformTextInputModifierNode {
66 return LegacyAdaptingPlatformTextInputModifierNode(
67 serviceAdapter,
68 legacyTextFieldState,
69 textFieldSelectionManager
70 )
71 }
72
73 override fun update(node: LegacyAdaptingPlatformTextInputModifierNode) {
74 node.setServiceAdapter(serviceAdapter)
75 node.legacyTextFieldState = legacyTextFieldState
76 node.textFieldSelectionManager = textFieldSelectionManager
77 }
78
79 override fun InspectorInfo.inspectableProperties() {
80 // Not a public-facing modifier.
81 }
82 }
83
84 /**
85 * Exposes [PlatformTextInputModifierNode] capabilities and other information from the modifier
86 * system such as position and some composition locals to implementations of
87 * [LegacyPlatformTextInputServiceAdapter].
88 */
89 internal class LegacyAdaptingPlatformTextInputModifierNode(
90 private var serviceAdapter: LegacyPlatformTextInputServiceAdapter,
91 override var legacyTextFieldState: LegacyTextFieldState,
92 override var textFieldSelectionManager: TextFieldSelectionManager
93 ) :
94 Modifier.Node(),
95 PlatformTextInputModifierNode,
96 CompositionLocalConsumerModifierNode,
97 GlobalPositionAwareModifierNode,
98 LegacyPlatformTextInputServiceAdapter.LegacyPlatformTextInputNode {
99
100 override var layoutCoordinates: LayoutCoordinates? by mutableStateOf(null)
101 private set
102
103 override val softwareKeyboardController: SoftwareKeyboardController?
104 get() = currentValueOf(LocalSoftwareKeyboardController)
105
setServiceAdapternull106 fun setServiceAdapter(serviceAdapter: LegacyPlatformTextInputServiceAdapter) {
107 if (isAttached) {
108 this.serviceAdapter.stopInput()
109 this.serviceAdapter.unregisterModifier(this)
110 }
111 this.serviceAdapter = serviceAdapter
112 if (isAttached) {
113 this.serviceAdapter.registerModifier(this)
114 }
115 }
116
117 override val viewConfiguration: ViewConfiguration
118 get() = currentValueOf(LocalViewConfiguration)
119
onAttachnull120 override fun onAttach() {
121 serviceAdapter.registerModifier(this)
122 }
123
onDetachnull124 override fun onDetach() {
125 serviceAdapter.unregisterModifier(this)
126 }
127
onGloballyPositionednull128 override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
129 this.layoutCoordinates = coordinates
130 }
131
launchTextInputSessionnull132 override fun launchTextInputSession(
133 block: suspend PlatformTextInputSession.() -> Nothing
134 ): Job? {
135 if (!isAttached) return null
136 return coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) {
137 establishTextInputSession(block)
138 }
139 }
140 }
141