| author | |
| committer | |
| log | b44c1ce5d25ee0d5a5be82e380618a886c19ea96 |
| tree | 6be2ea4890b87173f64e976d8bc2c5fa92be269c |
| parent | 9accc567670da5096e9a694c1adb0d35adb51d9d |
| signature | Signed by PGP key B5690EEEBB952194 |
2 files changed, 94 insertions(+), 1 deletions(-)
apps/mobile/modules/t3-composer-editor/android/src/main/java/expo/modules/t3composereditor/T3ComposerEditorView.kt+35-1| ... | ... | @@ -12,6 +12,7 @@ import android.text.Editable |
| 12 | 12 | import android.text.InputType |
| 13 | 13 | import android.text.InputFilter |
| 14 | 14 | import android.text.Spanned |
| 15 | import android.text.TextUtils | |
| 15 | 16 | import android.text.TextWatcher |
| 16 | 17 | import android.text.style.ReplacementSpan |
| 17 | 18 | import android.util.TypedValue |
| ... | ... | @@ -254,7 +255,7 @@ class T3ComposerEditorView(context: Context, appContext: AppContext) : ExpoView( |
| 254 | 255 | } |
| 255 | 256 | |
| 256 | 257 | fun setPlaceholder(placeholder: String) { |
| 257 | editor.hint = placeholder | |
| 258 | editor.placeholder = placeholder | |
| 258 | 259 | } |
| 259 | 260 | |
| 260 | 261 | fun setClipboardFragment(fragment: String) { |
| ... | ... | @@ -267,12 +268,14 @@ class T3ComposerEditorView(context: Context, appContext: AppContext) : ExpoView( |
| 267 | 268 | } else { |
| 268 | 269 | Typeface.DEFAULT |
| 269 | 270 | } |
| 271 | editor.applyPlaceholder() | |
| 270 | 272 | } |
| 271 | 273 | |
| 272 | 274 | fun setFontSize(fontSize: Float) { |
| 273 | 275 | editor.textSize = fontSize |
| 274 | 276 | applyLineHeight() |
| 275 | 277 | applyTokenSpans() |
| 278 | editor.applyPlaceholder() | |
| 276 | 279 | } |
| 277 | 280 | |
| 278 | 281 | fun setLineHeight(lineHeight: Float) { |
| ... | ... | @@ -587,6 +590,37 @@ internal class SelectionAwareEditText(context: Context) : EditText(context) { |
| 587 | 590 | var maxInputChars = Int.MAX_VALUE |
| 588 | 591 | var clipboardFragment = "" |
| 589 | 592 | |
| 593 | /** | |
| 594 | * Placeholder shown while the draft is empty. An editable TextView never ellipsizes its hint, | |
| 595 | * so a long placeholder wraps once a wide system font or a large font scale (Samsung defaults) | |
| 596 | * runs out of width, and the resting composer grows to two lines. The hint is instead cut to | |
| 597 | * one line with an ellipsis for whatever width the editor is measured at. | |
| 598 | */ | |
| 599 | var placeholder = "" | |
| 600 | set(value) { | |
| 601 | field = value | |
| 602 | applyPlaceholder() | |
| 603 | } | |
| 604 | ||
| 605 | fun applyPlaceholder(availableWidth: Int = width - compoundPaddingLeft - compoundPaddingRight) { | |
| 606 | val next = | |
| 607 | if (availableWidth > 0) { | |
| 608 | TextUtils.ellipsize(placeholder, paint, availableWidth.toFloat(), TextUtils.TruncateAt.END) | |
| 609 | } else { | |
| 610 | placeholder | |
| 611 | } | |
| 612 | if (hint?.toString() != next.toString()) hint = next | |
| 613 | } | |
| 614 | ||
| 615 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { | |
| 616 | if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) { | |
| 617 | applyPlaceholder( | |
| 618 | MeasureSpec.getSize(widthMeasureSpec) - compoundPaddingLeft - compoundPaddingRight | |
| 619 | ) | |
| 620 | } | |
| 621 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) | |
| 622 | } | |
| 623 | ||
| 590 | 624 | private fun deleteChip(backwards: Boolean): Boolean { |
| 591 | 625 | val content = text |
| 592 | 626 | val start = minOf(selectionStart, selectionEnd) |
apps/mobile/modules/t3-composer-editor/android/src/test/java/expo/modules/t3composereditor/ComposerPlaceholderTest.kt created+59| ... | ... | @@ -0,0 +1,59 @@ |
| 1 | package expo.modules.t3composereditor | |
| 2 | ||
| 3 | import android.view.View | |
| 4 | import android.view.ViewGroup | |
| 5 | import org.junit.Assert.assertEquals | |
| 6 | import org.junit.Assert.assertTrue | |
| 7 | import org.junit.Test | |
| 8 | import org.junit.runner.RunWith | |
| 9 | import org.robolectric.RobolectricTestRunner | |
| 10 | import org.robolectric.RuntimeEnvironment | |
| 11 | import org.robolectric.annotation.Config | |
| 12 | import org.robolectric.annotation.GraphicsMode | |
| 13 | ||
| 14 | @RunWith(RobolectricTestRunner::class) | |
| 15 | @Config(sdk = [36], manifest = Config.NONE) | |
| 16 | @GraphicsMode(GraphicsMode.Mode.NATIVE) | |
| 17 | class ComposerPlaceholderTest { | |
| 18 | private val placeholder = "Ask the repo agent, or run a command…" | |
| 19 | private val editor = SelectionAwareEditText(RuntimeEnvironment.getApplication()).apply { | |
| 20 | layoutParams = ViewGroup.LayoutParams( | |
| 21 | ViewGroup.LayoutParams.MATCH_PARENT, | |
| 22 | ViewGroup.LayoutParams.MATCH_PARENT, | |
| 23 | ) | |
| 24 | textSize = 16f | |
| 25 | placeholder = this@ComposerPlaceholderTest.placeholder | |
| 26 | } | |
| 27 | ||
| 28 | private fun measureAt(width: Int) { | |
| 29 | editor.measure( | |
| 30 | View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), | |
| 31 | View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), | |
| 32 | ) | |
| 33 | editor.layout(0, 0, width, editor.measuredHeight) | |
| 34 | } | |
| 35 | ||
| 36 | @Test | |
| 37 | fun narrowEditorCutsThePlaceholderToOneLine() { | |
| 38 | val fullWidth = editor.paint.measureText(placeholder) | |
| 39 | val width = (fullWidth / 2).toInt() | |
| 40 | measureAt(width) | |
| 41 | ||
| 42 | val hint = editor.hint.toString() | |
| 43 | assertTrue(hint, hint.endsWith("…") && hint.length < placeholder.length) | |
| 44 | assertTrue(hint, editor.paint.measureText(hint) <= width) | |
| 45 | } | |
| 46 | ||
| 47 | @Test | |
| 48 | fun wideEditorKeepsTheWholePlaceholder() { | |
| 49 | measureAt(editor.paint.measureText(placeholder).toInt() + 20) | |
| 50 | assertEquals(placeholder, editor.hint.toString()) | |
| 51 | } | |
| 52 | ||
| 53 | @Test | |
| 54 | fun placeholderRecoversWhenTheEditorWidens() { | |
| 55 | measureAt((editor.paint.measureText(placeholder) / 2).toInt()) | |
| 56 | measureAt(editor.paint.measureText(placeholder).toInt() + 20) | |
| 57 | assertEquals(placeholder, editor.hint.toString()) | |
| 58 | } | |
| 59 | } |