From b84c803db441aede4fcd58f813fa72f14eaa000c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D0=B8=D1=80=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Fri, 6 Sep 2024 14:56:04 +0300 Subject: [PATCH 1/9] wip --- .../text/LongPressTextDragObserver.kt | 3 +- .../selection/TextFieldSelectionState.kt | 2 + .../TextFieldMagnifierNode.desktop.kt} | 1 - .../TextFieldMagnifierNode.jsWasm.kt | 40 +++ .../selection/TextFieldMagnifierNode.macos.kt | 40 +++ ...CupertinoTextFieldPointerModifier.skiko.kt | 2 +- .../text/selection/SelectionHandles.skiko.kt | 4 +- .../compose/foundation/Magnifier.uikit.kt | 10 +- .../selection/TextFieldMagnifierNode.uikit.kt | 235 ++++++++++++++++++ .../TextFieldSelectionManager.uikit.kt | 11 +- .../compose/mpp/demo/textfield/TextFields.kt | 97 +++++++- .../UIKitPlatformTextInputSession.uikit.kt | 46 ++++ leanback/leanback-paging/build.gradle | 6 +- paging/paging-common/build.gradle | 2 +- paging/paging-compose/build.gradle | 2 +- paging/paging-guava/build.gradle | 2 +- paging/paging-runtime/build.gradle | 2 +- paging/paging-rxjava2/build.gradle | 2 +- paging/paging-rxjava3/build.gradle | 2 +- paging/paging-testing/build.gradle | 2 +- settings.gradle | 2 +- 21 files changed, 477 insertions(+), 36 deletions(-) rename compose/foundation/foundation/src/{skikoMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.skiko.kt => desktopMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.desktop.kt} (93%) create mode 100644 compose/foundation/foundation/src/jsWasmMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.jsWasm.kt create mode 100644 compose/foundation/foundation/src/macosMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.macos.kt create mode 100644 compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.uikit.kt create mode 100644 compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/platform/UIKitPlatformTextInputSession.uikit.kt diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/LongPressTextDragObserver.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/LongPressTextDragObserver.kt index b33fd0e4fc26b..932006698b72b 100644 --- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/LongPressTextDragObserver.kt +++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/LongPressTextDragObserver.kt @@ -86,8 +86,7 @@ internal suspend fun PointerInputScope.detectDownAndDragGesturesWithObserver( launch(start = CoroutineStart.UNDISPATCHED) { detectDragGesturesWithObserver(observer) }.invokeOnCompletion { - // Otherwise observer won't be notified if - // composable was disposed before the drag cancellation + // b/288931376: detectDragGestures do not call onDragCancel when composable is disposed. if (it is CancellationException){ observer.onCancel() } diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionState.kt index 0c74a299a1501..f1a3eac1a83a5 100644 --- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionState.kt +++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldSelectionState.kt @@ -407,6 +407,8 @@ internal class TextFieldSelectionState( clearHandleDragging() } ) + }.invokeOnCompletion { + clearHandleDragging() } launch(start = CoroutineStart.UNDISPATCHED) { detectSelectionHandleDragGestures(isStartHandle) diff --git a/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.skiko.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.desktop.kt similarity index 93% rename from compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.skiko.kt rename to compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.desktop.kt index ab8ec6fcd6576..3a64a3c92fbe5 100644 --- a/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.skiko.kt +++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.desktop.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.text.input.internal.TransformedTextFieldState * Initializes either an actual TextFieldMagnifierNode implementation or No-op node according to * whether magnifier is supported. */ -// TODO https://youtrack.jetbrains.com/issue/COMPOSE-737/TextField2.-Implement-textFieldMagnifierNode internal actual fun textFieldMagnifierNode( textFieldState: TransformedTextFieldState, textFieldSelectionState: TextFieldSelectionState, diff --git a/compose/foundation/foundation/src/jsWasmMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.jsWasm.kt b/compose/foundation/foundation/src/jsWasmMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.jsWasm.kt new file mode 100644 index 0000000000000..3a64a3c92fbe5 --- /dev/null +++ b/compose/foundation/foundation/src/jsWasmMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.jsWasm.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.foundation.text.input.internal.selection + +import androidx.compose.foundation.text.input.internal.TextLayoutState +import androidx.compose.foundation.text.input.internal.TransformedTextFieldState + +/** + * Initializes either an actual TextFieldMagnifierNode implementation or No-op node according to + * whether magnifier is supported. + */ +internal actual fun textFieldMagnifierNode( + textFieldState: TransformedTextFieldState, + textFieldSelectionState: TextFieldSelectionState, + textLayoutState: TextLayoutState, + visible: Boolean +): TextFieldMagnifierNode { + return object : TextFieldMagnifierNode() { + override fun update( + textFieldState: TransformedTextFieldState, + textFieldSelectionState: TextFieldSelectionState, + textLayoutState: TextLayoutState, + visible: Boolean + ) {} + } +} \ No newline at end of file diff --git a/compose/foundation/foundation/src/macosMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.macos.kt b/compose/foundation/foundation/src/macosMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.macos.kt new file mode 100644 index 0000000000000..3a64a3c92fbe5 --- /dev/null +++ b/compose/foundation/foundation/src/macosMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.macos.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.foundation.text.input.internal.selection + +import androidx.compose.foundation.text.input.internal.TextLayoutState +import androidx.compose.foundation.text.input.internal.TransformedTextFieldState + +/** + * Initializes either an actual TextFieldMagnifierNode implementation or No-op node according to + * whether magnifier is supported. + */ +internal actual fun textFieldMagnifierNode( + textFieldState: TransformedTextFieldState, + textFieldSelectionState: TextFieldSelectionState, + textLayoutState: TextLayoutState, + visible: Boolean +): TextFieldMagnifierNode { + return object : TextFieldMagnifierNode() { + override fun update( + textFieldState: TransformedTextFieldState, + textFieldSelectionState: TextFieldSelectionState, + textLayoutState: TextLayoutState, + visible: Boolean + ) {} + } +} \ No newline at end of file diff --git a/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/CupertinoTextFieldPointerModifier.skiko.kt b/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/CupertinoTextFieldPointerModifier.skiko.kt index dde2bf1739f06..97f50e15a72c7 100644 --- a/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/CupertinoTextFieldPointerModifier.skiko.kt +++ b/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/CupertinoTextFieldPointerModifier.skiko.kt @@ -189,7 +189,7 @@ private fun getLongPressHandlerModifier( var dragBeginOffset = Offset.Zero override fun onStart(startPoint: Offset) { - currentManager.draggingHandle = Handle.SelectionEnd + currentManager.draggingHandle = Handle.Cursor currentManager.currentDragPosition = startPoint currentState.layoutResult?.let { layoutResult -> diff --git a/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/selection/SelectionHandles.skiko.kt b/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/selection/SelectionHandles.skiko.kt index 41d4348dd47f8..413e5e082a39a 100644 --- a/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/selection/SelectionHandles.skiko.kt +++ b/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/selection/SelectionHandles.skiko.kt @@ -23,6 +23,7 @@ import androidx.compose.foundation.text.textFieldPointer import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.drawWithCache @@ -160,6 +161,7 @@ internal fun Modifier.drawSelectionHandle( } } +@OptIn(ExperimentalComposeUiApi::class) @Composable internal fun HandlePopup( positionProvider: OffsetProvider, @@ -171,7 +173,7 @@ internal fun HandlePopup( } Popup( popupPositionProvider = popupPositionProvider, - properties = PopupProperties(clippingEnabled = false), + properties = PopupProperties(clippingEnabled = false, usePlatformInsets = false), content = content, ) } diff --git a/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/Magnifier.uikit.kt b/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/Magnifier.uikit.kt index aac1a5ebce26a..c4dd2fa4c8958 100644 --- a/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/Magnifier.uikit.kt +++ b/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/Magnifier.uikit.kt @@ -213,11 +213,11 @@ internal class MagnifierNode( private var previousSize: IntSize? = null fun update( - sourceCenter: Density.() -> Offset, - magnifierCenter: (Density.() -> Offset)?, - onSizeChanged: ((DpSize) -> Unit)?, - color: Color, - platformMagnifierFactory: PlatformMagnifierFactory + sourceCenter: Density.() -> Offset = this.sourceCenter, + magnifierCenter: (Density.() -> Offset)? = this.magnifierCenter, + onSizeChanged: ((DpSize) -> Unit)? = this.onSizeChanged, + color: Color = this.color, + platformMagnifierFactory: PlatformMagnifierFactory = this.platformMagnifierFactory ) { val previousPlatformMagnifierFactory = this.platformMagnifierFactory val previousColor = this.color diff --git a/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.uikit.kt b/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.uikit.kt new file mode 100644 index 0000000000000..a2c91c0db0a11 --- /dev/null +++ b/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.uikit.kt @@ -0,0 +1,235 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.foundation.text.input.internal.selection + +import androidx.compose.foundation.MagnifierNode +import androidx.compose.foundation.isPlatformMagnifierSupported +import androidx.compose.foundation.text.Handle +import androidx.compose.foundation.text.input.internal.TextLayoutState +import androidx.compose.foundation.text.input.internal.TransformedTextFieldState +import androidx.compose.foundation.text.input.internal.fromTextLayoutToCore +import androidx.compose.foundation.text.selection.LocalTextSelectionColors +import androidx.compose.foundation.text.selection.MagnifierPostTravelDp +import androidx.compose.foundation.text.selection.visibleBounds +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.isSpecified +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.node.CompositionLocalConsumerModifierNode +import androidx.compose.ui.node.ObserverModifierNode +import androidx.compose.ui.node.currentValueOf +import androidx.compose.ui.node.observeReads +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.semantics.SemanticsPropertyReceiver +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.IntSize +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +/** + * Initializes either an actual TextFieldMagnifierNode implementation or No-op node according to + * whether magnifier is supported. + */ +internal actual fun textFieldMagnifierNode( + textFieldState: TransformedTextFieldState, + textFieldSelectionState: TextFieldSelectionState, + textLayoutState: TextLayoutState, + visible: Boolean +): TextFieldMagnifierNode { + return if (isPlatformMagnifierSupported()) { + TextFieldMagnifierNodeImpl( + textFieldState = textFieldState, + textFieldSelectionState = textFieldSelectionState, + textLayoutState = textLayoutState, + visible = visible + ) + } else { + object : TextFieldMagnifierNode() { + override fun update( + textFieldState: TransformedTextFieldState, + textFieldSelectionState: TextFieldSelectionState, + textLayoutState: TextLayoutState, + visible: Boolean + ) { + } + } + } +} + +internal class TextFieldMagnifierNodeImpl( + private var textFieldState: TransformedTextFieldState, + private var textFieldSelectionState: TextFieldSelectionState, + private var textLayoutState: TextLayoutState, + private var visible: Boolean +) : TextFieldMagnifierNode(), + ObserverModifierNode, + CompositionLocalConsumerModifierNode { + + private var sourceCenter by mutableStateOf(Offset.Zero) + private var color by mutableStateOf(Color.Unspecified) + private var density by mutableStateOf(Density(1f,1f)) + private var magnifierSize by mutableStateOf(DpSize.Zero) + + private val magnifierNode = delegate( + MagnifierNode( + sourceCenter = { sourceCenter }, + onSizeChanged = { magnifierSize = it }, + ) + ) + + private var positioningJob : Job? = null + + override fun onAttach() { + super.onAttach() + onObservedReadsChanged() + restartPositionJob() + } + + override fun onObservedReadsChanged() { + observeReads { + color = currentValueOf(LocalTextSelectionColors).handleColor + density = currentValueOf(LocalDensity) + + magnifierNode.update(color = color) + } + } + + override fun update( + textFieldState: TransformedTextFieldState, + textFieldSelectionState: TextFieldSelectionState, + textLayoutState: TextLayoutState, + visible: Boolean + ) { + val previousTextFieldState = this.textFieldState + val previousSelectionState = this.textFieldSelectionState + val previousLayoutState = this.textLayoutState + val wasVisible = this.visible + + this.textFieldState = textFieldState + this.textFieldSelectionState = textFieldSelectionState + this.textLayoutState = textLayoutState + this.visible = visible + + if (textFieldState != previousTextFieldState || + textFieldSelectionState != previousSelectionState || + textLayoutState != previousLayoutState || + visible != wasVisible + ) { + restartPositionJob() + } + } + + private fun restartPositionJob() { + positioningJob?.cancel() + if (visible) { + positioningJob = coroutineScope.launch { + snapshotFlow { + calculateSelectionMagnifierCenterIOS( + textFieldState = textFieldState, + selectionState = textFieldSelectionState, + textLayoutState = textLayoutState, + magnifierSize = with(density) { + IntSize( + magnifierSize.width.roundToPx(), + magnifierSize.height.roundToPx(), + ) + }, + density = density.density + ) + }.collect { + sourceCenter = it + } + } + } else { + sourceCenter = Offset.Unspecified + } + } + + // TODO: Remove this once delegation can propagate this events on its own + override fun ContentDrawScope.draw() { + drawContent() + with(magnifierNode) { draw() } + } + + // TODO: Remove this once delegation can propagate this events on its own + override fun onGloballyPositioned(coordinates: LayoutCoordinates) { + magnifierNode.onGloballyPositioned(coordinates) + } + + // TODO: Remove this once delegation can propagate this events on its own + override fun SemanticsPropertyReceiver.applySemantics() { + with(magnifierNode) { applySemantics() } + } +} + +private fun calculateSelectionMagnifierCenterIOS( + textFieldState: TransformedTextFieldState, + selectionState: TextFieldSelectionState, + textLayoutState: TextLayoutState, + magnifierSize: IntSize, + density: Float +): Offset { + + val dragPosition = selectionState.handleDragPosition.takeIf { it.isSpecified } ?: + return Offset.Unspecified + + val selection = textFieldState.visualText.selection + + val textOffset = when (selectionState.draggingHandle) { + null -> return Offset.Unspecified + Handle.Cursor, + Handle.SelectionStart -> selection.start + Handle.SelectionEnd -> selection.end + } + + val layoutResult = textLayoutState.layoutResult ?: return Offset.Unspecified + + // hide magnifier when selection goes below the text field + if (dragPosition.y > layoutResult.lastBaseline + MagnifierPostTravelDp * density) { + return Offset.Unspecified + } + + val innerFieldBounds = textLayoutState.coreNodeCoordinates + ?.takeIf { it.isAttached }?.visibleBounds() + ?: return Offset.Unspecified + + // Center vertically on the current line. + val centerY = if (textFieldState.visualText.text.isNotEmpty()) { + val line = layoutResult.getLineForOffset(textOffset) + val top = layoutResult.getLineTop(line) + val bottom = layoutResult.getLineBottom(line) + ((bottom - top) / 2) + top + } else { + // can't get line bounds for empty field + // better alternatives? + innerFieldBounds.center.y + } + + // native magnifier goes a little bit farther than text field bounds + val centerX = dragPosition.x.coerceIn( + -magnifierSize.width / 4f, + innerFieldBounds.right + magnifierSize.width / 4 + ) + + return textLayoutState.fromTextLayoutToCore(Offset(centerX, centerY)) +} diff --git a/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.uikit.kt b/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.uikit.kt index 1bc597f0d58e0..e0438ba4ae2d6 100644 --- a/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.uikit.kt +++ b/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.uikit.kt @@ -20,7 +20,6 @@ import androidx.compose.foundation.PlatformMagnifierFactory import androidx.compose.foundation.isPlatformMagnifierSupported import androidx.compose.foundation.magnifier import androidx.compose.foundation.text.Handle -import androidx.compose.foundation.text.InternalFoundationTextApi import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -29,7 +28,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.isUnspecified import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.unit.IntSize internal actual fun Modifier.textFieldMagnifier(manager: TextFieldSelectionManager): Modifier { @@ -73,7 +74,6 @@ internal actual fun Modifier.textFieldMagnifier(manager: TextFieldSelectionManag // But! Compose text selection is a bit different from iOS: // when we select multiple lines below the selection start on iOS - we always see the caret / handle. // Compose caret in such scenario is always covered by finger so we don't actually see what do we select. -@OptIn(InternalFoundationTextApi::class) private fun calculateSelectionMagnifierCenterIOS( manager: TextFieldSelectionManager, magnifierSize: IntSize, @@ -107,7 +107,7 @@ private fun calculateSelectionMagnifierCenterIOS( .translateDecorationToInnerCoordinates(localDragPosition) // hide magnifier when selection goes below the text field - if (innerDragPosition.y > layoutResult.lastBaseline + HideThresholdDp * density) { + if (innerDragPosition.y > layoutResult.lastBaseline + MagnifierPostTravelDp * density) { return Offset.Unspecified } @@ -136,7 +136,10 @@ private fun calculateSelectionMagnifierCenterIOS( return Offset(centerX, centerY) } -private const val HideThresholdDp = 36 +/** + * Bottom drag point below the last text baseline after that magnifier is dismissed + * */ +internal const val MagnifierPostTravelDp = 36 /** * Multiplier for height tolerance calculation. diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/textfield/TextFields.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/textfield/TextFields.kt index 6a34887b15457..db811bb467d70 100644 --- a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/textfield/TextFields.kt +++ b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/textfield/TextFields.kt @@ -16,25 +16,39 @@ package androidx.compose.mpp.demo.textfield +import androidx.compose.foundation.border +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text import androidx.compose.material.TextField +import androidx.compose.material.TextFieldDefaults +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Star import androidx.compose.mpp.demo.Screen +import androidx.compose.mpp.demo.textfield.android.loremIpsum import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp val TextFields = Screen.Selection( "TextFields", @@ -80,17 +94,7 @@ val TextFields = Screen.Selection( }, Screen.Example("BasicTextField2") { - var textFieldState by remember { mutableStateOf("I am TextField") } - val textFieldState2 = remember { TextFieldState("I am TextField 2") } - Column(Modifier.fillMaxWidth()) { - BasicTextField( - textFieldState, - onValueChange = { textFieldState = it }, - Modifier.padding(16.dp).fillMaxWidth() - ) - Box(Modifier.height(16.dp)) - BasicTextField(textFieldState2, Modifier.padding(16.dp).fillMaxWidth()) - } + BasicTextField2() }, Screen.Example("RTL and BiDi") { @@ -114,3 +118,74 @@ private fun AlmostFullscreen() { Modifier.fillMaxSize().padding(vertical = 40.dp) ) } + +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun BasicTextField2(modifier: Modifier = Modifier) { + + val style = TextStyle(fontSize = 22.sp) + var textFieldState by remember { mutableStateOf("I am TextField") } + val textFieldState2 = remember { TextFieldState("I am TextField 2") } + Column(Modifier.fillMaxWidth()) { + BasicTextField( + textFieldState, + onValueChange = { textFieldState = it }, + Modifier.padding(16.dp).fillMaxWidth(), + textStyle = style + ) + Box(Modifier.height(16.dp)) + BasicTextField( + textFieldState2, Modifier.padding(16.dp).fillMaxWidth(), textStyle = style + ) + + val s3 = remember { TextFieldState(loremIpsum(wordCount = 10)) } + BasicTextField( + state = s3, + textStyle = style, + modifier = Modifier + .padding(16.dp) + .padding(start = 100.dp) + .width(200.dp) + .border(1.dp, Color.Black) + ) + + val s4 = remember { TextFieldState("Multiline 6\n" + loremIpsum(wordCount = 50)) } + BasicTextField( + state = s4, + textStyle = style, + modifier = Modifier.padding(16.dp).padding(start = 100.dp).width(200.dp) + .border(1.dp, Color.Black), + lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 6) + ) + + val s5 = remember { TextFieldState(loremIpsum(wordCount = 30)) } + + BasicTextField( + state = s5, + textStyle = style, + modifier = Modifier.padding(50.dp).fillMaxWidth(), + decorator = { + TextFieldDefaults.TextFieldDecorationBox( + innerTextField = it, + enabled = true, + interactionSource = remember { MutableInteractionSource() }, + singleLine = false, + value = s5.text.toString(), + visualTransformation = VisualTransformation.None, + leadingIcon = { + Icon( + imageVector = Icons.Default.Star, + contentDescription = null + ) + }, + trailingIcon = { + Icon( + imageVector = Icons.Default.Star, + contentDescription = null + ) + } + ) + } + ) + } +} \ No newline at end of file diff --git a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/platform/UIKitPlatformTextInputSession.uikit.kt b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/platform/UIKitPlatformTextInputSession.uikit.kt new file mode 100644 index 0000000000000..5a1906f496c4c --- /dev/null +++ b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/platform/UIKitPlatformTextInputSession.uikit.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.ui.platform + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.suspendCancellableCoroutine + +internal class UIKitPlatformTextInputSession( + private val textInputService: UIKitTextInputService, + private val coroutineScope: CoroutineScope +) : PlatformTextInputSessionScope, CoroutineScope by coroutineScope { + +// private val methodSessionMutex = SessionMutex() + + override suspend fun startInputMethod(request: PlatformTextInputMethodRequest): Nothing { + suspendCancellableCoroutine { continuation -> + // Show the keyboard and ask the IMM to restart input. + textInputService.startInput() + + // The cleanup needs to be executed synchronously, otherwise the stopInput call + // might come too late and end up overriding the next field's startInput. This + // is prevented by the queuing in TextInputService, but can still happen when + // focus transfers from a compose text field to a view-based text field + // (EditText). + continuation.invokeOnCancellation { + // If this session was cancelled because another session was requested, this + // call will be a noop. + textInputService.stopInput() + } + } + } +} \ No newline at end of file diff --git a/leanback/leanback-paging/build.gradle b/leanback/leanback-paging/build.gradle index 0bcaf3b06093f..6c77ce1b69e70 100644 --- a/leanback/leanback-paging/build.gradle +++ b/leanback/leanback-paging/build.gradle @@ -31,9 +31,9 @@ dependencies { // by gradle when leanback runs. But leanback uses paging-runtime:3.0.0. The version discrepancy // between paging-common and paging-runtime will cause errors. `Exclude` prevents pulling in // paging-common from internal-testutils-paging and uses the 3.0.0 version provided by leanback. - androidTestImplementation(project(":internal-testutils-paging")){ - exclude group: "androidx.paging" - } +// androidTestImplementation(project(":internal-testutils-paging")){ +// exclude group: "androidx.paging" +// } androidTestImplementation(libs.kotlinTest) androidTestImplementation(libs.kotlinCoroutinesTest) androidTestImplementation("androidx.arch.core:core-testing:2.2.0") diff --git a/paging/paging-common/build.gradle b/paging/paging-common/build.gradle index a480f2bf0be54..0efec9a5b66c2 100644 --- a/paging/paging-common/build.gradle +++ b/paging/paging-common/build.gradle @@ -68,7 +68,7 @@ androidXMultiplatform { implementation(libs.kotlinCoroutinesTest) implementation(libs.kotlinTest) implementation(project(":kruth:kruth")) - implementation(project(":internal-testutils-paging")) +// implementation(project(":internal-testutils-paging")) } } diff --git a/paging/paging-compose/build.gradle b/paging/paging-compose/build.gradle index eb8a9eaee1b1a..d9b1607ce745c 100644 --- a/paging/paging-compose/build.gradle +++ b/paging/paging-compose/build.gradle @@ -48,7 +48,7 @@ androidXMultiplatform { dependencies { implementation projectOrArtifact(":compose:ui:ui-tooling") implementation(project(":compose:test-utils")) - implementation(projectOrArtifact(":internal-testutils-paging")) +// implementation(projectOrArtifact(":internal-testutils-paging")) } } diff --git a/paging/paging-guava/build.gradle b/paging/paging-guava/build.gradle index 7ce3e9b90001f..bf29f96337128 100644 --- a/paging/paging-guava/build.gradle +++ b/paging/paging-guava/build.gradle @@ -30,7 +30,7 @@ dependencies { implementation(libs.kotlinCoroutinesGuava) testImplementation(project(":internal-testutils-common")) - testImplementation(project(":internal-testutils-paging")) +// testImplementation(project(":internal-testutils-paging")) testImplementation(libs.junit) testImplementation(libs.kotlinTest) testImplementation(libs.kotlinCoroutinesTest) diff --git a/paging/paging-runtime/build.gradle b/paging/paging-runtime/build.gradle index a80973cadc478..3eb3f738ea4a3 100644 --- a/paging/paging-runtime/build.gradle +++ b/paging/paging-runtime/build.gradle @@ -45,7 +45,7 @@ dependencies { androidTestImplementation(project(":internal-testutils-common")) androidTestImplementation(project(":internal-testutils-ktx")) - androidTestImplementation(project(":internal-testutils-paging")) +// androidTestImplementation(project(":internal-testutils-paging")) androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-runtime-testing")) androidTestImplementation(libs.testCore) androidTestImplementation(libs.testExtJunit) diff --git a/paging/paging-rxjava2/build.gradle b/paging/paging-rxjava2/build.gradle index c23998e94970d..c1d3fc74d32c6 100644 --- a/paging/paging-rxjava2/build.gradle +++ b/paging/paging-rxjava2/build.gradle @@ -31,7 +31,7 @@ dependencies { implementation(libs.kotlinCoroutinesRx2) testImplementation(project(":internal-testutils-common")) - testImplementation(project(":internal-testutils-paging")) +// testImplementation(project(":internal-testutils-paging")) testImplementation(project(":internal-testutils-ktx")) testImplementation(libs.junit) testImplementation(libs.kotlinTest) diff --git a/paging/paging-rxjava3/build.gradle b/paging/paging-rxjava3/build.gradle index f193372d1d2ce..b0e17ea9ec62c 100644 --- a/paging/paging-rxjava3/build.gradle +++ b/paging/paging-rxjava3/build.gradle @@ -31,7 +31,7 @@ dependencies { implementation(libs.kotlinCoroutinesRx3) testImplementation(project(":internal-testutils-common")) - testImplementation(project(":internal-testutils-paging")) +// testImplementation(project(":internal-testutils-paging")) testImplementation(project(":internal-testutils-ktx")) testImplementation(libs.junit) testImplementation(libs.kotlinTest) diff --git a/paging/paging-testing/build.gradle b/paging/paging-testing/build.gradle index 2955b5f9807c3..defc55778475d 100644 --- a/paging/paging-testing/build.gradle +++ b/paging/paging-testing/build.gradle @@ -62,7 +62,7 @@ androidXMultiplatform { dependencies { implementation(libs.kotlinTest) implementation(libs.kotlinCoroutinesTest) - implementation(project(":internal-testutils-paging")) +// implementation(project(":internal-testutils-paging")) implementation(project(":kruth:kruth")) } } diff --git a/settings.gradle b/settings.gradle index a3effec5e6f48..15564e9466aaf 100644 --- a/settings.gradle +++ b/settings.gradle @@ -252,7 +252,7 @@ includeProject(":internal-testutils-fonts", "testutils/testutils-fonts") includeProject(":internal-testutils-truth", "testutils/testutils-truth") includeProject(":internal-testutils-ktx", "testutils/testutils-ktx") includeProject(":internal-testutils-navigation", "testutils/testutils-navigation") -includeProject(":internal-testutils-paging", "testutils/testutils-paging") +//includeProject(":internal-testutils-paging", "testutils/testutils-paging") includeProject(":internal-testutils-gradle-plugin", "testutils/testutils-gradle-plugin") includeProject(":internal-testutils-mockito", "testutils/testutils-mockito") From d13c49779b7cb72d8966d67cea24f617981e2f0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D0=B8=D1=80=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Sun, 8 Sep 2024 14:17:48 +0300 Subject: [PATCH 2/9] cleanup --- .../text/selection/SelectionHandles.skiko.kt | 3 +- .../UIKitPlatformTextInputSession.uikit.kt | 46 ------------------- 2 files changed, 1 insertion(+), 48 deletions(-) delete mode 100644 compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/platform/UIKitPlatformTextInputSession.uikit.kt diff --git a/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/selection/SelectionHandles.skiko.kt b/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/selection/SelectionHandles.skiko.kt index 413e5e082a39a..1226fb1267bb1 100644 --- a/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/selection/SelectionHandles.skiko.kt +++ b/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/selection/SelectionHandles.skiko.kt @@ -161,7 +161,6 @@ internal fun Modifier.drawSelectionHandle( } } -@OptIn(ExperimentalComposeUiApi::class) @Composable internal fun HandlePopup( positionProvider: OffsetProvider, @@ -173,7 +172,7 @@ internal fun HandlePopup( } Popup( popupPositionProvider = popupPositionProvider, - properties = PopupProperties(clippingEnabled = false, usePlatformInsets = false), + properties = PopupProperties(clippingEnabled = false), content = content, ) } diff --git a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/platform/UIKitPlatformTextInputSession.uikit.kt b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/platform/UIKitPlatformTextInputSession.uikit.kt deleted file mode 100644 index 5a1906f496c4c..0000000000000 --- a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/platform/UIKitPlatformTextInputSession.uikit.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.compose.ui.platform - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.suspendCancellableCoroutine - -internal class UIKitPlatformTextInputSession( - private val textInputService: UIKitTextInputService, - private val coroutineScope: CoroutineScope -) : PlatformTextInputSessionScope, CoroutineScope by coroutineScope { - -// private val methodSessionMutex = SessionMutex() - - override suspend fun startInputMethod(request: PlatformTextInputMethodRequest): Nothing { - suspendCancellableCoroutine { continuation -> - // Show the keyboard and ask the IMM to restart input. - textInputService.startInput() - - // The cleanup needs to be executed synchronously, otherwise the stopInput call - // might come too late and end up overriding the next field's startInput. This - // is prevented by the queuing in TextInputService, but can still happen when - // focus transfers from a compose text field to a view-based text field - // (EditText). - continuation.invokeOnCancellation { - // If this session was cancelled because another session was requested, this - // call will be a noop. - textInputService.stopInput() - } - } - } -} \ No newline at end of file From 163e9a8437d8d0a86c992c271c176122c59dcf1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D0=B8=D1=80=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Sun, 8 Sep 2024 15:30:36 +0300 Subject: [PATCH 3/9] fix scrollable positioning --- ...CupertinoTextFieldPointerModifier.skiko.kt | 2 +- .../selection/TextFieldMagnifierNode.uikit.kt | 31 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/CupertinoTextFieldPointerModifier.skiko.kt b/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/CupertinoTextFieldPointerModifier.skiko.kt index 97f50e15a72c7..dde2bf1739f06 100644 --- a/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/CupertinoTextFieldPointerModifier.skiko.kt +++ b/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/CupertinoTextFieldPointerModifier.skiko.kt @@ -189,7 +189,7 @@ private fun getLongPressHandlerModifier( var dragBeginOffset = Offset.Zero override fun onStart(startPoint: Offset) { - currentManager.draggingHandle = Handle.Cursor + currentManager.draggingHandle = Handle.SelectionEnd currentManager.currentDragPosition = startPoint currentState.layoutResult?.let { layoutResult -> diff --git a/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.uikit.kt b/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.uikit.kt index a2c91c0db0a11..1d271db9ce465 100644 --- a/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.uikit.kt +++ b/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.uikit.kt @@ -21,6 +21,7 @@ import androidx.compose.foundation.isPlatformMagnifierSupported import androidx.compose.foundation.text.Handle import androidx.compose.foundation.text.input.internal.TextLayoutState import androidx.compose.foundation.text.input.internal.TransformedTextFieldState +import androidx.compose.foundation.text.input.internal.coerceIn import androidx.compose.foundation.text.input.internal.fromTextLayoutToCore import androidx.compose.foundation.text.selection.LocalTextSelectionColors import androidx.compose.foundation.text.selection.MagnifierPostTravelDp @@ -190,8 +191,8 @@ private fun calculateSelectionMagnifierCenterIOS( density: Float ): Offset { - val dragPosition = selectionState.handleDragPosition.takeIf { it.isSpecified } ?: - return Offset.Unspecified + val dragPosition = + selectionState.handleDragPosition.takeIf { it.isSpecified } ?: return Offset.Unspecified val selection = textFieldState.visualText.selection @@ -199,6 +200,7 @@ private fun calculateSelectionMagnifierCenterIOS( null -> return Offset.Unspecified Handle.Cursor, Handle.SelectionStart -> selection.start + Handle.SelectionEnd -> selection.end } @@ -209,7 +211,7 @@ private fun calculateSelectionMagnifierCenterIOS( return Offset.Unspecified } - val innerFieldBounds = textLayoutState.coreNodeCoordinates + val coreNodeBounds = textLayoutState.coreNodeCoordinates ?.takeIf { it.isAttached }?.visibleBounds() ?: return Offset.Unspecified @@ -218,18 +220,21 @@ private fun calculateSelectionMagnifierCenterIOS( val line = layoutResult.getLineForOffset(textOffset) val top = layoutResult.getLineTop(line) val bottom = layoutResult.getLineBottom(line) - ((bottom - top) / 2) + top + (((bottom - top) / 2) + top) } else { - // can't get line bounds for empty field - // better alternatives? - innerFieldBounds.center.y + coreNodeBounds.center.y } - // native magnifier goes a little bit farther than text field bounds - val centerX = dragPosition.x.coerceIn( - -magnifierSize.width / 4f, - innerFieldBounds.right + magnifierSize.width / 4 - ) + val offset = textLayoutState.fromTextLayoutToCore(Offset(dragPosition.x, centerY)) - return textLayoutState.fromTextLayoutToCore(Offset(centerX, centerY)) + return offset.copy( + x = offset.x.coerceIn( + -magnifierSize.width / 4f, + coreNodeBounds.right + magnifierSize.width / 4 + ), + y = offset.y.coerceIn( + coreNodeBounds.top, + coreNodeBounds.bottom + ) + ) } From f20a5b05e517bd0f30f1f3c94312a23c952a8a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D0=B8=D1=80=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Sun, 8 Sep 2024 15:33:54 +0300 Subject: [PATCH 4/9] rollback irrelevant changes --- .../compose/mpp/demo/textfield/TextFields.kt | 97 +++---------------- leanback/leanback-paging/build.gradle | 6 +- paging/paging-common/build.gradle | 2 +- paging/paging-compose/build.gradle | 2 +- paging/paging-guava/build.gradle | 2 +- paging/paging-runtime/build.gradle | 2 +- paging/paging-rxjava2/build.gradle | 2 +- paging/paging-rxjava3/build.gradle | 2 +- paging/paging-testing/build.gradle | 2 +- settings.gradle | 2 +- 10 files changed, 22 insertions(+), 97 deletions(-) diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/textfield/TextFields.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/textfield/TextFields.kt index db811bb467d70..3006abfdba94c 100644 --- a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/textfield/TextFields.kt +++ b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/textfield/TextFields.kt @@ -16,39 +16,25 @@ package androidx.compose.mpp.demo.textfield -import androidx.compose.foundation.border -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.BasicTextField -import androidx.compose.foundation.text.input.TextFieldLineLimits import androidx.compose.foundation.text.input.TextFieldState -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Icon import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text import androidx.compose.material.TextField -import androidx.compose.material.TextFieldDefaults -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Star import androidx.compose.mpp.demo.Screen -import androidx.compose.mpp.demo.textfield.android.loremIpsum import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp val TextFields = Screen.Selection( "TextFields", @@ -94,7 +80,17 @@ val TextFields = Screen.Selection( }, Screen.Example("BasicTextField2") { - BasicTextField2() + var textFieldState by remember { mutableStateOf("I am TextField") } + val textFieldState2 = remember { TextFieldState("I am TextField 2") } + Column(Modifier.fillMaxWidth()) { + BasicTextField( + textFieldState, + onValueChange = { textFieldState = it }, + Modifier.padding(16.dp).fillMaxWidth() + ) + Box(Modifier.height(16.dp)) + BasicTextField(textFieldState2, Modifier.padding(16.dp).fillMaxWidth()) + } }, Screen.Example("RTL and BiDi") { @@ -117,75 +113,4 @@ private fun AlmostFullscreen() { textState.value, { textState.value = it }, Modifier.fillMaxSize().padding(vertical = 40.dp) ) -} - -@OptIn(ExperimentalMaterialApi::class) -@Composable -private fun BasicTextField2(modifier: Modifier = Modifier) { - - val style = TextStyle(fontSize = 22.sp) - var textFieldState by remember { mutableStateOf("I am TextField") } - val textFieldState2 = remember { TextFieldState("I am TextField 2") } - Column(Modifier.fillMaxWidth()) { - BasicTextField( - textFieldState, - onValueChange = { textFieldState = it }, - Modifier.padding(16.dp).fillMaxWidth(), - textStyle = style - ) - Box(Modifier.height(16.dp)) - BasicTextField( - textFieldState2, Modifier.padding(16.dp).fillMaxWidth(), textStyle = style - ) - - val s3 = remember { TextFieldState(loremIpsum(wordCount = 10)) } - BasicTextField( - state = s3, - textStyle = style, - modifier = Modifier - .padding(16.dp) - .padding(start = 100.dp) - .width(200.dp) - .border(1.dp, Color.Black) - ) - - val s4 = remember { TextFieldState("Multiline 6\n" + loremIpsum(wordCount = 50)) } - BasicTextField( - state = s4, - textStyle = style, - modifier = Modifier.padding(16.dp).padding(start = 100.dp).width(200.dp) - .border(1.dp, Color.Black), - lineLimits = TextFieldLineLimits.MultiLine(maxHeightInLines = 6) - ) - - val s5 = remember { TextFieldState(loremIpsum(wordCount = 30)) } - - BasicTextField( - state = s5, - textStyle = style, - modifier = Modifier.padding(50.dp).fillMaxWidth(), - decorator = { - TextFieldDefaults.TextFieldDecorationBox( - innerTextField = it, - enabled = true, - interactionSource = remember { MutableInteractionSource() }, - singleLine = false, - value = s5.text.toString(), - visualTransformation = VisualTransformation.None, - leadingIcon = { - Icon( - imageVector = Icons.Default.Star, - contentDescription = null - ) - }, - trailingIcon = { - Icon( - imageVector = Icons.Default.Star, - contentDescription = null - ) - } - ) - } - ) - } } \ No newline at end of file diff --git a/leanback/leanback-paging/build.gradle b/leanback/leanback-paging/build.gradle index 6c77ce1b69e70..0bcaf3b06093f 100644 --- a/leanback/leanback-paging/build.gradle +++ b/leanback/leanback-paging/build.gradle @@ -31,9 +31,9 @@ dependencies { // by gradle when leanback runs. But leanback uses paging-runtime:3.0.0. The version discrepancy // between paging-common and paging-runtime will cause errors. `Exclude` prevents pulling in // paging-common from internal-testutils-paging and uses the 3.0.0 version provided by leanback. -// androidTestImplementation(project(":internal-testutils-paging")){ -// exclude group: "androidx.paging" -// } + androidTestImplementation(project(":internal-testutils-paging")){ + exclude group: "androidx.paging" + } androidTestImplementation(libs.kotlinTest) androidTestImplementation(libs.kotlinCoroutinesTest) androidTestImplementation("androidx.arch.core:core-testing:2.2.0") diff --git a/paging/paging-common/build.gradle b/paging/paging-common/build.gradle index 0efec9a5b66c2..a480f2bf0be54 100644 --- a/paging/paging-common/build.gradle +++ b/paging/paging-common/build.gradle @@ -68,7 +68,7 @@ androidXMultiplatform { implementation(libs.kotlinCoroutinesTest) implementation(libs.kotlinTest) implementation(project(":kruth:kruth")) -// implementation(project(":internal-testutils-paging")) + implementation(project(":internal-testutils-paging")) } } diff --git a/paging/paging-compose/build.gradle b/paging/paging-compose/build.gradle index d9b1607ce745c..eb8a9eaee1b1a 100644 --- a/paging/paging-compose/build.gradle +++ b/paging/paging-compose/build.gradle @@ -48,7 +48,7 @@ androidXMultiplatform { dependencies { implementation projectOrArtifact(":compose:ui:ui-tooling") implementation(project(":compose:test-utils")) -// implementation(projectOrArtifact(":internal-testutils-paging")) + implementation(projectOrArtifact(":internal-testutils-paging")) } } diff --git a/paging/paging-guava/build.gradle b/paging/paging-guava/build.gradle index bf29f96337128..7ce3e9b90001f 100644 --- a/paging/paging-guava/build.gradle +++ b/paging/paging-guava/build.gradle @@ -30,7 +30,7 @@ dependencies { implementation(libs.kotlinCoroutinesGuava) testImplementation(project(":internal-testutils-common")) -// testImplementation(project(":internal-testutils-paging")) + testImplementation(project(":internal-testutils-paging")) testImplementation(libs.junit) testImplementation(libs.kotlinTest) testImplementation(libs.kotlinCoroutinesTest) diff --git a/paging/paging-runtime/build.gradle b/paging/paging-runtime/build.gradle index 3eb3f738ea4a3..a80973cadc478 100644 --- a/paging/paging-runtime/build.gradle +++ b/paging/paging-runtime/build.gradle @@ -45,7 +45,7 @@ dependencies { androidTestImplementation(project(":internal-testutils-common")) androidTestImplementation(project(":internal-testutils-ktx")) -// androidTestImplementation(project(":internal-testutils-paging")) + androidTestImplementation(project(":internal-testutils-paging")) androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-runtime-testing")) androidTestImplementation(libs.testCore) androidTestImplementation(libs.testExtJunit) diff --git a/paging/paging-rxjava2/build.gradle b/paging/paging-rxjava2/build.gradle index c1d3fc74d32c6..c23998e94970d 100644 --- a/paging/paging-rxjava2/build.gradle +++ b/paging/paging-rxjava2/build.gradle @@ -31,7 +31,7 @@ dependencies { implementation(libs.kotlinCoroutinesRx2) testImplementation(project(":internal-testutils-common")) -// testImplementation(project(":internal-testutils-paging")) + testImplementation(project(":internal-testutils-paging")) testImplementation(project(":internal-testutils-ktx")) testImplementation(libs.junit) testImplementation(libs.kotlinTest) diff --git a/paging/paging-rxjava3/build.gradle b/paging/paging-rxjava3/build.gradle index b0e17ea9ec62c..f193372d1d2ce 100644 --- a/paging/paging-rxjava3/build.gradle +++ b/paging/paging-rxjava3/build.gradle @@ -31,7 +31,7 @@ dependencies { implementation(libs.kotlinCoroutinesRx3) testImplementation(project(":internal-testutils-common")) -// testImplementation(project(":internal-testutils-paging")) + testImplementation(project(":internal-testutils-paging")) testImplementation(project(":internal-testutils-ktx")) testImplementation(libs.junit) testImplementation(libs.kotlinTest) diff --git a/paging/paging-testing/build.gradle b/paging/paging-testing/build.gradle index defc55778475d..2955b5f9807c3 100644 --- a/paging/paging-testing/build.gradle +++ b/paging/paging-testing/build.gradle @@ -62,7 +62,7 @@ androidXMultiplatform { dependencies { implementation(libs.kotlinTest) implementation(libs.kotlinCoroutinesTest) -// implementation(project(":internal-testutils-paging")) + implementation(project(":internal-testutils-paging")) implementation(project(":kruth:kruth")) } } diff --git a/settings.gradle b/settings.gradle index 15564e9466aaf..a3effec5e6f48 100644 --- a/settings.gradle +++ b/settings.gradle @@ -252,7 +252,7 @@ includeProject(":internal-testutils-fonts", "testutils/testutils-fonts") includeProject(":internal-testutils-truth", "testutils/testutils-truth") includeProject(":internal-testutils-ktx", "testutils/testutils-ktx") includeProject(":internal-testutils-navigation", "testutils/testutils-navigation") -//includeProject(":internal-testutils-paging", "testutils/testutils-paging") +includeProject(":internal-testutils-paging", "testutils/testutils-paging") includeProject(":internal-testutils-gradle-plugin", "testutils/testutils-gradle-plugin") includeProject(":internal-testutils-mockito", "testutils/testutils-mockito") From dcf99eb3aa7cfd523f9e43ef0089e6bb9bde3d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D0=B8=D1=80=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Sun, 8 Sep 2024 15:34:46 +0300 Subject: [PATCH 5/9] rollback irrelevant changes --- .../kotlin/androidx/compose/mpp/demo/textfield/TextFields.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/textfield/TextFields.kt b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/textfield/TextFields.kt index 3006abfdba94c..6a34887b15457 100644 --- a/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/textfield/TextFields.kt +++ b/compose/mpp/demo/src/commonMain/kotlin/androidx/compose/mpp/demo/textfield/TextFields.kt @@ -113,4 +113,4 @@ private fun AlmostFullscreen() { textState.value, { textState.value = it }, Modifier.fillMaxSize().padding(vertical = 40.dp) ) -} \ No newline at end of file +} From 44c3752cd9df47e64c8bc71f21cb0bc7e6ba4962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D0=B8=D1=80=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Sun, 8 Sep 2024 16:14:05 +0300 Subject: [PATCH 6/9] rollback irrelevant changes --- .../compose/foundation/text/selection/SelectionHandles.skiko.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/selection/SelectionHandles.skiko.kt b/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/selection/SelectionHandles.skiko.kt index 1226fb1267bb1..41d4348dd47f8 100644 --- a/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/selection/SelectionHandles.skiko.kt +++ b/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/text/selection/SelectionHandles.skiko.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.text.textFieldPointer import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.drawWithCache From e940dc2f3a549b6310bfd391aa9194888290df1d Mon Sep 17 00:00:00 2001 From: Alexander Zhirkevich Date: Mon, 9 Sep 2024 11:10:16 +0300 Subject: [PATCH 7/9] Update TextFieldMagnifierNode.uikit.kt --- .../input/internal/selection/TextFieldMagnifierNode.uikit.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.uikit.kt b/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.uikit.kt index 1d271db9ce465..81a83474494d8 100644 --- a/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.uikit.kt +++ b/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.uikit.kt @@ -86,7 +86,7 @@ internal class TextFieldMagnifierNodeImpl( ObserverModifierNode, CompositionLocalConsumerModifierNode { - private var sourceCenter by mutableStateOf(Offset.Zero) + private var sourceCenter by mutableStateOf(Offset.Unspecified) private var color by mutableStateOf(Color.Unspecified) private var density by mutableStateOf(Density(1f,1f)) private var magnifierSize by mutableStateOf(DpSize.Zero) From b746241ae4c87f75127704157b6a12c39d5fecc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D0=B8=D1=80=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Mon, 9 Sep 2024 18:43:38 +0300 Subject: [PATCH 8/9] dp constant --- .../internal/selection/TextFieldMagnifierNode.uikit.kt | 9 ++++----- .../text/selection/TextFieldSelectionManager.uikit.kt | 7 +++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.uikit.kt b/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.uikit.kt index 1d271db9ce465..d1d95a8df56f0 100644 --- a/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.uikit.kt +++ b/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/input/internal/selection/TextFieldMagnifierNode.uikit.kt @@ -21,10 +21,9 @@ import androidx.compose.foundation.isPlatformMagnifierSupported import androidx.compose.foundation.text.Handle import androidx.compose.foundation.text.input.internal.TextLayoutState import androidx.compose.foundation.text.input.internal.TransformedTextFieldState -import androidx.compose.foundation.text.input.internal.coerceIn import androidx.compose.foundation.text.input.internal.fromTextLayoutToCore import androidx.compose.foundation.text.selection.LocalTextSelectionColors -import androidx.compose.foundation.text.selection.MagnifierPostTravelDp +import androidx.compose.foundation.text.selection.MagnifierPostTravel import androidx.compose.foundation.text.selection.visibleBounds import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -207,7 +206,7 @@ private fun calculateSelectionMagnifierCenterIOS( val layoutResult = textLayoutState.layoutResult ?: return Offset.Unspecified // hide magnifier when selection goes below the text field - if (dragPosition.y > layoutResult.lastBaseline + MagnifierPostTravelDp * density) { + if (dragPosition.y > layoutResult.lastBaseline + MagnifierPostTravel.value * density) { return Offset.Unspecified } @@ -220,14 +219,14 @@ private fun calculateSelectionMagnifierCenterIOS( val line = layoutResult.getLineForOffset(textOffset) val top = layoutResult.getLineTop(line) val bottom = layoutResult.getLineBottom(line) - (((bottom - top) / 2) + top) + ((bottom - top) / 2) + top } else { coreNodeBounds.center.y } val offset = textLayoutState.fromTextLayoutToCore(Offset(dragPosition.x, centerY)) - return offset.copy( + return Offset( x = offset.x.coerceIn( -magnifierSize.width / 4f, coreNodeBounds.right + magnifierSize.width / 4 diff --git a/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.uikit.kt b/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.uikit.kt index e0438ba4ae2d6..b259800adb745 100644 --- a/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.uikit.kt +++ b/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.uikit.kt @@ -28,10 +28,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.geometry.isUnspecified import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp internal actual fun Modifier.textFieldMagnifier(manager: TextFieldSelectionManager): Modifier { if (!isPlatformMagnifierSupported()) { @@ -107,7 +106,7 @@ private fun calculateSelectionMagnifierCenterIOS( .translateDecorationToInnerCoordinates(localDragPosition) // hide magnifier when selection goes below the text field - if (innerDragPosition.y > layoutResult.lastBaseline + MagnifierPostTravelDp * density) { + if (innerDragPosition.y > layoutResult.lastBaseline + MagnifierPostTravel.value * density) { return Offset.Unspecified } @@ -139,7 +138,7 @@ private fun calculateSelectionMagnifierCenterIOS( /** * Bottom drag point below the last text baseline after that magnifier is dismissed * */ -internal const val MagnifierPostTravelDp = 36 +internal val MagnifierPostTravel = 36.dp /** * Multiplier for height tolerance calculation. From 01ba411814f720bf4f7545944e24909f8f260f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D0=B8=D1=80=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Mon, 9 Sep 2024 18:44:22 +0300 Subject: [PATCH 9/9] remove unused code --- .../kotlin/androidx/compose/foundation/Magnifier.uikit.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/Magnifier.uikit.kt b/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/Magnifier.uikit.kt index c4dd2fa4c8958..009b742c098a7 100644 --- a/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/Magnifier.uikit.kt +++ b/compose/foundation/foundation/src/uikitMain/kotlin/androidx/compose/foundation/Magnifier.uikit.kt @@ -318,9 +318,9 @@ internal class MagnifierNode( if (sourceCenterInView != null) { // Calculate magnifier center if it's provided. Only accept if the returned value is // specified. Then add [anchorPositionInWindow] for relative positioning. - magnifierCenter?.invoke(density) - ?.takeIf { it.isSpecified } - ?.let { anchorPositionInWindow + it } + //magnifierCenter?.invoke(density) + // ?.takeIf { it.isSpecified } + // ?.let { anchorPositionInWindow + it } magnifier.update(sourceCenter = sourceCenterInView) updateSizeIfNecessary()