Skip to content

Commit 1d5979f

Browse files
authored
Adopt PlatformTextInputService2 (#2990)
Extend the functionality of the `TextEditingScope` and adjust its behaviour to align with the `EditProcessor`'s commands. Apply `recomposeAndLayout` after edit commands to recalculate the text and layout state. Refactor the `UIKitTextInputService` to use the `PlatformTextInputMethodRequest` directly to bypass issues caused by delays in mutable state propagation. Add the `UIKitTextInputServiceAdapter` to support the deprecated `PlatformTextInputService`. Fixes https://youtrack.jetbrains.com/issue/CMP-7832/iOS-Adopt-PlatformTextInputService2 Fixes https://youtrack.jetbrains.com/issue/CMP-7983/BasicTextFieldTextFieldState-inputTransformation-reports-inconsistent-changes-between-Android-and-iOS Fixes https://youtrack.jetbrains.com/issue/CMP-9566/BasicTextField-w-InputTransformation-incorrect-internal-state-on-iOS ## Release Notes ### Fixes - iOS - Fix issue where `BasicTextField` reported inconsistent changes between Android and iOS - Fix issue where a `BasicTextField` with `InputTransformation` may use the incorrect internal text state.
1 parent eabcbcd commit 1d5979f

15 files changed

Lines changed: 1112 additions & 437 deletions

File tree

compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/input/internal/LegacyPlatformTextInputServiceAdapter.skiko.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ import androidx.compose.ui.text.input.FinishComposingTextCommand
3232
import androidx.compose.ui.text.input.ImeAction
3333
import androidx.compose.ui.text.input.ImeOptions
3434
import androidx.compose.ui.text.input.OffsetMapping
35+
import androidx.compose.ui.text.input.SetComposingRegionCommand
3536
import androidx.compose.ui.text.input.SetComposingTextCommand
37+
import androidx.compose.ui.text.input.SetSelectionCommand
3638
import androidx.compose.ui.text.input.TextEditingScope
3739
import androidx.compose.ui.text.input.TextEditorState
3840
import androidx.compose.ui.text.input.TextFieldValue
@@ -114,6 +116,7 @@ internal actual fun createLegacyPlatformTextInputServiceAdapter():
114116
override fun get(index: Int): Char = textFieldValue.text[index]
115117
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence =
116118
textFieldValue.text.subSequence(startIndex, endIndex)
119+
override val text: String get() = textFieldValue.text
117120
}
118121

119122
val editBlock: (block: TextEditingScope.() -> Unit) -> Unit = { block ->
@@ -152,6 +155,12 @@ private fun TextEditingScope(commands: MutableList<EditCommand>) = object : Text
152155
)
153156
}
154157

158+
override fun setSelection(start: Int, end: Int) {
159+
commands.add(
160+
SetSelectionCommand(start, end)
161+
)
162+
}
163+
155164
override fun commitText(
156165
text: CharSequence,
157166
newCursorPosition: Int
@@ -161,6 +170,12 @@ private fun TextEditingScope(commands: MutableList<EditCommand>) = object : Text
161170
)
162171
}
163172

173+
override fun setComposingRegion(start: Int, end: Int) {
174+
commands.add(
175+
SetComposingRegionCommand(start, end)
176+
)
177+
}
178+
164179
override fun setComposingText(
165180
text: CharSequence,
166181
newCursorPosition: Int

compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/input/internal/TextInputSession.skiko.kt

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -143,18 +143,11 @@ private inline fun (() -> TextFieldCharSequence).asTextEditorState() = object :
143143
override val composition: TextRange?
144144
get() = this@asTextEditorState().composition
145145

146-
override fun toString(): String = this@asTextEditorState().toString()
147-
146+
override val text: String get() = this@asTextEditorState().toString()
148147
}
149148

150149
@OptIn(ExperimentalComposeUiApi::class)
151150
private fun TextEditingScope(buffer: TextFieldBuffer) = object : TextEditingScope {
152-
private var TextFieldBuffer.cursor: Int
153-
get() = if (selection.collapsed) selection.end else -1
154-
set(value) {
155-
setSelectionCoerced(value, value)
156-
}
157-
158151
// Be careful about using TextRange.start/end, as the selection can be reversed (start > end).
159152
// Prefer to use TextRange.min/max.
160153

@@ -180,15 +173,17 @@ private fun TextEditingScope(buffer: TextFieldBuffer) = object : TextEditingScop
180173
)
181174
}
182175

176+
override fun setSelection(start: Int, end: Int) {
177+
buffer.setSelectionCoerced(start, end)
178+
}
179+
183180
override fun commitText(text: CharSequence, newCursorPosition: Int) {
184181
// API description says replace ongoing composition text if there. Then, if there is no
185182
// composition text, insert text into the cursor position or replace selection.
186183
val replacementRange = buffer.composition ?: buffer.selection
187184
buffer.replace(replacementRange.min, replacementRange.max, text)
188185

189-
// After replace function is called, the editing buffer places the cursor at the end of the
190-
// modified range.
191-
val newCursor = buffer.cursor
186+
val newCursor = replacementRange.min + text.length
192187

193188
// See API description for the meaning of newCursorPosition.
194189
val newCursorInBuffer =
@@ -200,19 +195,21 @@ private fun TextEditingScope(buffer: TextFieldBuffer) = object : TextEditingScop
200195
buffer.setSelectionCoerced(newCursorInBuffer, newCursorInBuffer)
201196
}
202197

198+
override fun setComposingRegion(start: Int, end: Int) {
199+
buffer.setComposition(start, end)
200+
}
201+
203202
override fun setComposingText(text: CharSequence, newCursorPosition: Int) {
204203
val replacementRange = buffer.composition ?: buffer.selection
205204
// API doc says, if there is ongoing composing text, replace it with new text.
206205
// If there is no composing text, insert composing text into the cursor position with
207206
// removing selected text if any.
208207
buffer.replace(replacementRange.min, replacementRange.max, text)
209208
if (text.isNotEmpty()) {
210-
buffer.setComposition(replacementRange.min, replacementRange.max + text.length)
209+
buffer.setComposition(replacementRange.min, replacementRange.min + text.length)
211210
}
212211

213-
// After replace function is called, the editing buffer places the cursor at the end of the
214-
// modified range.
215-
val newCursor = buffer.cursor
212+
val newCursor = replacementRange.min + text.length
216213

217214
// See API description for the meaning of newCursorPosition.
218215
val newCursorInBuffer =
@@ -222,7 +219,7 @@ private fun TextEditingScope(buffer: TextFieldBuffer) = object : TextEditingScop
222219
newCursor + newCursorPosition - text.length
223220
}
224221

225-
buffer.cursor = newCursorInBuffer
222+
buffer.setSelectionCoerced(newCursorInBuffer, newCursorInBuffer)
226223
}
227224

228225
override fun finishComposingText() {

0 commit comments

Comments
 (0)